├── .github ├── ISSUE_TEMPLATE │ └── release-checklist.md ├── dependabot.yml └── workflows │ ├── require-release-note.yml │ └── rust.yml ├── .gitignore ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE-2.0 ├── LICENSE-MIT ├── README.md ├── build.rs ├── docs ├── development.md └── release-notes.md ├── examples ├── check-backdoor.rs ├── get-guestinfo.rs ├── log.rs └── report-agent.rs └── src ├── asm ├── mod.rs └── x86_64-linux.s ├── backdoor.rs ├── erpc.rs ├── error.rs ├── lib.rs └── low_bw.rs /.github/ISSUE_TEMPLATE/release-checklist.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: release checklist 3 | about: release checklist template 4 | title: New release for vmw_backdoor-rs 5 | labels: jira,kind/release 6 | warning: | 7 | ⚠️ Template generated by https://github.com/coreos/repo-templates; do not edit downstream 8 | --- 9 | 10 | # Release process 11 | 12 | This project uses [cargo-release][cargo-release] in order to prepare new releases, tag and sign the relevant git commit, and publish the resulting artifacts to [crates.io][crates-io]. 13 | The release process follows the usual PR-and-review flow, allowing an external reviewer to have a final check before publishing. 14 | 15 | ## Requirements 16 | 17 | This guide requires: 18 | 19 | * A web browser (and network connectivity) 20 | * `git` 21 | * [GPG setup][GPG setup] and personal key for signing 22 | * `cargo` (suggested: latest stable toolchain from [rustup][rustup]) 23 | * `cargo-release` (suggested: `cargo install -f cargo-release`) 24 | * Write access to this GitHub project 25 | * A verified account on crates.io 26 | * Membership in the [Fedora CoreOS Crates Owners group](https://github.com/orgs/coreos/teams/fedora-coreos-crates-owners/members), which will give you upload access to crates.io 27 | 28 | ## Release checklist 29 | 30 | These steps show how to release version `x.y.z` on the `origin` remote (this can be checked via `git remote -av`). 31 | Push access to the upstream repository is required in order to publish the new tag and the PR branch. 32 | 33 | :warning:: if `origin` is not the name of the locally configured remote that points to the upstream git repository (i.e. `git@github.com:coreos/vmw_backdoor-rs.git`), be sure to assign the correct remote name to the `UPSTREAM_REMOTE` variable. 34 | 35 | - prepare environment: 36 | - [ ] `RELEASE_VER=x.y.z` 37 | - [ ] `UPSTREAM_REMOTE=origin` 38 | - [ ] `git checkout -b pre-release-${RELEASE_VER}` 39 | 40 | - check `Cargo.toml` for unintended increases of lower version bounds: 41 | - [ ] `git diff $(git describe --abbrev=0) Cargo.toml` 42 | 43 | - write release notes: 44 | - [ ] write release notes in `docs/release-notes.md` 45 | - [ ] `git add docs/release-notes.md && git commit -m "docs/release-notes: update for release ${RELEASE_VER}"` 46 | 47 | - land the changes: 48 | - [ ] PR the changes, get them reviewed, approved and merged 49 | - [ ] if doing a branched release, also include a PR to merge the `docs/release-notes.md` changes into main 50 | 51 | - make sure the project is clean: 52 | - [ ] Make sure `cargo-release` is up to date: `cargo install cargo-release` 53 | - [ ] `git checkout main && git pull ${UPSTREAM_REMOTE} main` 54 | - [ ] `cargo test --all-features` 55 | - [ ] `cargo clean` 56 | - [ ] `git clean -fd` 57 | 58 | - create release commit on a dedicated branch and tag it (the commit and tag will be signed with the GPG signing key you configured): 59 | - [ ] `git checkout -b release-${RELEASE_VER}` 60 | - [ ] `cargo release --execute ${RELEASE_VER}` (and confirm the version when prompted) 61 | 62 | - open and merge a PR for this release: 63 | - [ ] `git push ${UPSTREAM_REMOTE} release-${RELEASE_VER}` 64 | - [ ] open a web browser and create a PR for the branch above 65 | - [ ] make sure the resulting PR contains exactly one commit 66 | - [ ] get the PR reviewed, approved and merged 67 | 68 | - publish the artifacts (tag and crate): 69 | - [ ] `git checkout v${RELEASE_VER}` 70 | - [ ] verify that `grep "^version = \"${RELEASE_VER}\"$" Cargo.toml` produces output 71 | - [ ] `git push ${UPSTREAM_REMOTE} v${RELEASE_VER}` 72 | - [ ] `cargo publish` 73 | 74 | - publish this release on GitHub: 75 | - [ ] find the new tag in the [GitHub tag list](https://github.com/coreos/vmw_backdoor-rs/tags), click the triple dots menu, and create a release for it 76 | - [ ] copy in the changelog from the release notes doc 77 | - [ ] publish release 78 | 79 | - clean up the local environment (optional, but recommended): 80 | - [ ] `cargo clean` 81 | - [ ] `git checkout main` 82 | - [ ] `git pull ${UPSTREAM_REMOTE} main` 83 | - [ ] `git push ${UPSTREAM_REMOTE} :pre-release-${RELEASE_VER} :release-${RELEASE_VER}` 84 | - [ ] `git branch -d pre-release-${RELEASE_VER} release-${RELEASE_VER}` 85 | 86 | - Fedora packaging: 87 | - [ ] update the `rust-vmw_backdoor` spec file in [Fedora](https://src.fedoraproject.org/rpms/rust-vmw_backdoor) 88 | - bump the `Version` 89 | - switch the `Release` back to `1%{?dist}` 90 | - remove any patches obsoleted by the new release 91 | - update changelog 92 | - [ ] run `spectool -g -S rust-vmw_backdoor.spec` 93 | - [ ] run `kinit your_fas_account@FEDORAPROJECT.ORG` 94 | - [ ] run `fedpkg new-sources $(spectool -S rust-vmw_backdoor.spec | sed 's:.*/::')` 95 | - [ ] PR the changes in [Fedora](https://src.fedoraproject.org/rpms/rust-vmw_backdoor) 96 | - [ ] once the PR merges to rawhide, merge rawhide into the other relevant branches (e.g. f41) then push those, for example: 97 | ```bash 98 | git checkout rawhide 99 | git pull --ff-only 100 | git checkout f41 101 | git merge --ff-only rawhide 102 | git push origin f41 103 | ``` 104 | - [ ] on each of those branches run `fedpkg build` 105 | - [ ] once the builds have finished, submit them to [bodhi](https://bodhi.fedoraproject.org/updates/new), filling in: 106 | - `rust-vmw_backdoor` for `Packages` 107 | - selecting the build(s) that just completed, except for the rawhide one (which gets submitted automatically) 108 | - writing brief release notes like "New upstream release; see release notes at `link to GitHub release`" 109 | - leave `Update name` blank 110 | - `Type`, `Severity` and `Suggestion` can be left as `unspecified` unless it is a security release. In that case select `security` with the appropriate severity. 111 | - `Stable karma` and `Unstable` karma can be set to `2` and `-1`, respectively. 112 | 113 | [cargo-release]: https://github.com/sunng87/cargo-release 114 | [rustup]: https://rustup.rs/ 115 | [crates-io]: https://crates.io/ 116 | [GPG setup]: https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification 117 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Maintained in https://github.com/coreos/repo-templates 2 | # Do not edit downstream. 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | labels: ["skip-notes"] 11 | open-pull-requests-limit: 3 12 | - package-ecosystem: cargo 13 | directory: / 14 | schedule: 15 | interval: monthly 16 | open-pull-requests-limit: 10 17 | labels: 18 | - dependency 19 | - skip-notes 20 | -------------------------------------------------------------------------------- /.github/workflows/require-release-note.yml: -------------------------------------------------------------------------------- 1 | # Maintained in https://github.com/coreos/repo-templates 2 | # Do not edit downstream. 3 | 4 | name: Release notes 5 | 6 | on: 7 | pull_request: 8 | branches: [main] 9 | types: [opened, synchronize, reopened, labeled, unlabeled] 10 | 11 | permissions: 12 | contents: read 13 | 14 | concurrency: 15 | group: release-note-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | require-notes: 20 | name: Require release note 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Require release-notes.md update 24 | uses: coreos/actions-lib/require-file-change@main 25 | with: 26 | path: docs/release-notes.md 27 | override-label: skip-notes 28 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # Maintained in https://github.com/coreos/repo-templates 2 | # Do not edit downstream. 3 | 4 | name: Rust 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | permissions: 11 | contents: read 12 | 13 | # don't waste job slots on superseded code 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | env: 19 | CARGO_TERM_COLOR: always 20 | # Pinned toolchain for linting 21 | ACTIONS_LINTS_TOOLCHAIN: 1.75.0 22 | 23 | jobs: 24 | tests-stable: 25 | name: Tests, stable toolchain 26 | runs-on: ubuntu-latest 27 | container: quay.io/coreos-assembler/fcos-buildroot:testing-devel 28 | steps: 29 | - name: Check out repository 30 | uses: actions/checkout@v4 31 | - name: Install toolchain 32 | uses: dtolnay/rust-toolchain@v1 33 | with: 34 | toolchain: stable 35 | - name: Cache build artifacts 36 | uses: Swatinem/rust-cache@v2 37 | - name: cargo build 38 | run: cargo build --all-targets 39 | - name: cargo test 40 | run: cargo test --all-targets 41 | tests-release-stable: 42 | name: Tests (release), stable toolchain 43 | runs-on: ubuntu-latest 44 | container: quay.io/coreos-assembler/fcos-buildroot:testing-devel 45 | steps: 46 | - name: Check out repository 47 | uses: actions/checkout@v4 48 | - name: Install toolchain 49 | uses: dtolnay/rust-toolchain@v1 50 | with: 51 | toolchain: stable 52 | - name: Cache build artifacts 53 | uses: Swatinem/rust-cache@v2 54 | - name: cargo build (release) 55 | run: cargo build --all-targets --release 56 | - name: cargo test (release) 57 | run: cargo test --all-targets --release 58 | tests-release-msrv: 59 | name: Tests (release), minimum supported toolchain 60 | runs-on: ubuntu-latest 61 | container: quay.io/coreos-assembler/fcos-buildroot:testing-devel 62 | steps: 63 | - name: Check out repository 64 | uses: actions/checkout@v4 65 | - name: Detect crate MSRV 66 | run: | 67 | msrv=$(cargo metadata --format-version 1 --no-deps | \ 68 | jq -r '.packages[0].rust_version') 69 | echo "Crate MSRV: $msrv" 70 | echo "MSRV=$msrv" >> $GITHUB_ENV 71 | - name: Install toolchain 72 | uses: dtolnay/rust-toolchain@v1 73 | with: 74 | toolchain: ${{ env.MSRV }} 75 | - name: Cache build artifacts 76 | uses: Swatinem/rust-cache@v2 77 | - name: cargo build (release) 78 | run: cargo build --all-targets --release 79 | - name: cargo test (release) 80 | run: cargo test --all-targets --release 81 | linting: 82 | name: Lints, pinned toolchain 83 | runs-on: ubuntu-latest 84 | container: quay.io/coreos-assembler/fcos-buildroot:testing-devel 85 | steps: 86 | - name: Check out repository 87 | uses: actions/checkout@v4 88 | - name: Install toolchain 89 | uses: dtolnay/rust-toolchain@v1 90 | with: 91 | toolchain: ${{ env.ACTIONS_LINTS_TOOLCHAIN }} 92 | components: rustfmt, clippy 93 | - name: Cache build artifacts 94 | uses: Swatinem/rust-cache@v2 95 | - name: cargo fmt (check) 96 | run: cargo fmt -- --check -l 97 | - name: cargo clippy (warnings) 98 | run: cargo clippy --all-targets -- -D warnings 99 | tests-other-channels: 100 | name: Tests, unstable toolchain 101 | runs-on: ubuntu-latest 102 | container: quay.io/coreos-assembler/fcos-buildroot:testing-devel 103 | continue-on-error: true 104 | strategy: 105 | matrix: 106 | channel: [beta, nightly] 107 | steps: 108 | - name: Check out repository 109 | uses: actions/checkout@v4 110 | - name: Install toolchain 111 | uses: dtolnay/rust-toolchain@v1 112 | with: 113 | toolchain: ${{ matrix.channel }} 114 | - name: Cache build artifacts 115 | uses: Swatinem/rust-cache@v2 116 | - name: cargo build 117 | run: cargo build --all-targets 118 | - name: cargo test 119 | run: cargo test --all-targets 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: vmw_backdoor 3 | Source: https://www.github.com/coreos/vmw_backdoor 4 | 5 | Files: * 6 | Copyright: 2017-2022, Project contributors 7 | License: MIT or Apache-2.0 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vmw_backdoor" 3 | version = "0.2.4" 4 | authors = ["Luca BRUNO "] 5 | edition = "2021" 6 | rust-version = "1.66.0" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/coreos/vmw_backdoor-rs" 9 | documentation = "https://docs.rs/vmw_backdoor" 10 | description = 'A pure-Rust library for VMware host-guest protocol ("VMXh backdoor")' 11 | 12 | [dependencies] 13 | cfg-if = "^1.0" 14 | libc = "^0.2" 15 | errno = ">= 0.2, < 0.4" 16 | log = "^0.4" 17 | thiserror = "^1.0" 18 | 19 | [build-dependencies] 20 | cc = "^1.0" 21 | 22 | [package.metadata.release] 23 | pre-release-commit-message = "cargo: release {{version}}" 24 | publish = false 25 | push = false 26 | sign-commit = true 27 | sign-tag = true 28 | tag-message = "release {{version}}" 29 | -------------------------------------------------------------------------------- /LICENSE-APACHE-2.0: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining 2 | a copy of this software and associated documentation files (the 3 | "Software"), to deal in the Software without restriction, including 4 | without limitation the rights to use, copy, modify, merge, publish, 5 | distribute, sublicense, and/or sell copies of the Software, and to 6 | permit persons to whom the Software is furnished to do so, subject to 7 | the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be 10 | included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vmw\_backdoor 2 | 3 | [![crates.io](https://img.shields.io/crates/v/vmw_backdoor.svg)](https://crates.io/crates/vmw_backdoor) 4 | [![Documentation](https://docs.rs/vmw_backdoor/badge.svg)](https://docs.rs/vmw_backdoor) 5 | 6 | A pure-Rust library for VMware host-guest protocol ("VMXh backdoor"). 7 | 8 | This library provides helpers to access and use the VMware backdoor from a 9 | guest VM. It allows bi-directional interactions with the VMWare hypervisor 10 | and host environment. 11 | 12 | The "backdoor" protocol does not have official specifications, but it has been 13 | widely analyzed and there are multiple projects documenting it: 14 | 1. [https://github.com/vmware/open-vm-tools/blob/stable-11.0.5/open-vm-tools/lib/include/backdoor_def.h][1] 15 | 2. [https://wiki.osdev.org/VMware_tools][2] 16 | 3. [https://sysprogs.com/legacy/articles/kdvmware/guestrpc.shtml][3] 17 | 4. [https://github.com/vmware/vmw-guestinfo/tree/master/bdoor][4] 18 | 5. [https://sites.google.com/site/chitchatvmback/backdoor][5] 19 | 20 | [1]: https://github.com/vmware/open-vm-tools/blob/stable-11.0.5/open-vm-tools/lib/include/backdoor_def.h 21 | [2]: https://wiki.osdev.org/VMware_tools 22 | [3]: https://sysprogs.com/legacy/articles/kdvmware/guestrpc.shtml 23 | [4]: https://github.com/vmware/vmw-guestinfo/tree/master/bdoor 24 | [5]: https://sites.google.com/site/chitchatvmback/backdoor 25 | 26 | ## Example 27 | 28 | ```rust 29 | let is_vmw = vmw_backdoor::is_vmware_cpu(); 30 | println!("VMware CPU detected: {}.", is_vmw); 31 | 32 | let mut guard = vmw_backdoor::access_backdoor().unwrap(); 33 | println!("Raised I/O access to reach backdoor port."); 34 | 35 | let found = guard.probe_vmware_backdoor().unwrap_or(false); 36 | println!("VMware backdoor detected: {}.", found); 37 | 38 | let mut erpc = guard.open_enhanced_chan().unwrap(); 39 | let key = "guestinfo.ignition.config.data"; 40 | let guestinfo = erpc.get_guestinfo(key.as_bytes()).unwrap(); 41 | 42 | if let Some(val) = guestinfo { 43 | println!("Got value for key '{}':", key); 44 | println!("{}", String::from_utf8_lossy(&val)); 45 | }; 46 | ``` 47 | 48 | Some more examples are available under [examples](examples). 49 | 50 | ## License 51 | 52 | Licensed under either of 53 | 54 | * MIT license - 55 | * Apache License, Version 2.0 - 56 | 57 | at your option. 58 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | fn main() { 4 | let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH") 5 | .expect("missing $CARGO_CFG_TARGET_ARCH env variable"); 6 | let target_os = 7 | std::env::var("CARGO_CFG_TARGET_OS").expect("missing $CARGO_CFG_TARGET_OS env variable"); 8 | if target_arch == "x86_64" && target_os == "linux" { 9 | asm_x86_64_linux(); 10 | }; 11 | } 12 | 13 | fn asm_x86_64_linux() { 14 | // build directory for this crate 15 | let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); 16 | 17 | // extend the library search path 18 | println!("cargo:rustc-link-search={}", out_dir.display()); 19 | 20 | let src = "src/asm/x86_64-linux.s"; 21 | 22 | // assemble source 23 | cc::Build::new().file(src).compile("asm"); 24 | 25 | // rebuild if source changed 26 | println!("cargo:rerun-if-changed={}", src); 27 | } 28 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## Release process 4 | 5 | Releases can be performed by [creating a new release ticket][new-release-ticket] and following the steps in the checklist there. 6 | 7 | [new-release-ticket]: https://github.com/coreos/vmw_backdoor-rs/issues/new?template=release-checklist.md 8 | -------------------------------------------------------------------------------- /docs/release-notes.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | ## Upcoming vmw_backdoor 0.2.5 (unreleased) 4 | 5 | Changes: 6 | 7 | - Require Rust ≥ 1.66.0 8 | 9 | 10 | ## vmw_backdoor 0.2.4 (2023-06-01) 11 | 12 | Changes: 13 | 14 | - Require Rust ≥ 1.56.0 15 | - Support `errno` 0.3 16 | - Add release notes doc 17 | 18 | 19 | ## vmw_backdoor 0.2.3 (2022-11-29) 20 | 21 | Changes: 22 | 23 | - cargo: update all URLs to new location 24 | - cargo: update cargo-release metadata 25 | 26 | 27 | ## vmw_backdoor 0.2.1 (2021-04-19) 28 | 29 | Changes: 30 | 31 | - cargo: bump cfg-if dependency 32 | - build: fix target detection for cross-builds 33 | - ci: move Travis jobs to GH actions 34 | 35 | 36 | ## vmv_backdoor 0.2.0 (2020-10-08) 37 | 38 | Changes: 39 | 40 | - backdoor: use proper errno on iopl failures 41 | - backdoor: allow avoiding iopl permission errors 42 | 43 | 44 | ## vmv_backdoor 0.1.3 (2020-07-10) 45 | 46 | Changes: 47 | 48 | - asm: fix CPUID bitmask 49 | -------------------------------------------------------------------------------- /examples/check-backdoor.rs: -------------------------------------------------------------------------------- 1 | use vmw_backdoor as vmw; 2 | 3 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 4 | fn main() { 5 | let is_vmw = vmw::is_vmware_cpu(); 6 | println!("VMware CPU detected: {}.", is_vmw); 7 | 8 | let mut backdoor = vmw::access_backdoor_privileged().unwrap(); 9 | println!("Raised I/O access to reach backdoor port."); 10 | 11 | let found = match backdoor.probe_vmware_backdoor() { 12 | Ok(()) => true, 13 | Err(_) => false, 14 | }; 15 | println!("VMware backdoor detected: {}.", found); 16 | } 17 | 18 | #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] 19 | fn main() { 20 | eprintln!("Unsupported target"); 21 | } 22 | -------------------------------------------------------------------------------- /examples/get-guestinfo.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use vmw_backdoor as vmw; 3 | 4 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 5 | fn main() { 6 | let key = match env::args().collect::>().get(1) { 7 | Some(val) => val.clone(), 8 | None => panic!("missing argument: key name"), 9 | }; 10 | 11 | let is_vmw = vmw::is_vmware_cpu(); 12 | eprintln!("VMware CPU detected: {}.", is_vmw); 13 | if !is_vmw { 14 | panic!("Hypervisor not present"); 15 | } 16 | 17 | let mut backdoor = vmw::probe_backdoor_privileged().unwrap(); 18 | eprintln!("Got backdoor access."); 19 | 20 | let mut erpc = backdoor.open_enhanced_chan().unwrap(); 21 | eprintln!("Got ERPC channel: {:?}.", erpc); 22 | 23 | match erpc.get_guestinfo(key.as_bytes()).unwrap() { 24 | Some(val) => { 25 | eprintln!("Got value for key '{}'.", key); 26 | println!("{}", String::from_utf8_lossy(&val)); 27 | } 28 | None => panic!("Guestinfo property not found."), 29 | }; 30 | } 31 | 32 | #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] 33 | fn main() { 34 | eprintln!("Unsupported target"); 35 | } 36 | -------------------------------------------------------------------------------- /examples/log.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use vmw_backdoor as vmw; 3 | 4 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 5 | fn main() { 6 | let msg = match env::args().collect::>().get(1) { 7 | Some(val) => val.clone(), 8 | None => "Hello world (from vmw_backdoor)".to_string(), 9 | }; 10 | 11 | let is_vmw = vmw::is_vmware_cpu(); 12 | eprintln!("VMware CPU detected: {}.", is_vmw); 13 | if !is_vmw { 14 | panic!("Hypervisor not present"); 15 | } 16 | 17 | let mut backdoor = vmw::probe_backdoor_privileged().unwrap(); 18 | eprintln!("Got backdoor access."); 19 | 20 | let mut erpc = backdoor.open_enhanced_chan().unwrap(); 21 | eprintln!("Got ERPC channel: {:?}.", erpc); 22 | 23 | erpc.log(&msg).unwrap(); 24 | eprintln!("Sent log message: {}.", msg); 25 | } 26 | 27 | #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] 28 | fn main() { 29 | eprintln!("Unsupported target"); 30 | } 31 | -------------------------------------------------------------------------------- /examples/report-agent.rs: -------------------------------------------------------------------------------- 1 | use vmw_backdoor as vmw; 2 | 3 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 4 | fn main() { 5 | let is_vmw = vmw::is_vmware_cpu(); 6 | eprintln!("VMware CPU detected: {}.", is_vmw); 7 | if !is_vmw { 8 | panic!("Hypervisor not present"); 9 | } 10 | 11 | let mut backdoor = vmw::probe_backdoor_privileged().unwrap(); 12 | eprintln!("Got backdoor access."); 13 | 14 | let mut erpc = backdoor.open_enhanced_chan().unwrap(); 15 | eprintln!("Got ERPC channel: {:?}.", erpc); 16 | 17 | erpc.report_agent().unwrap(); 18 | eprintln!("Reported agent."); 19 | } 20 | 21 | #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] 22 | fn main() { 23 | eprintln!("Unsupported target"); 24 | } 25 | -------------------------------------------------------------------------------- /src/asm/mod.rs: -------------------------------------------------------------------------------- 1 | //! FFI bridge to ASM functions. 2 | 3 | use log::trace; 4 | 5 | extern "C" { 6 | /// External, low-bandwidth IN. 7 | fn _vmw_backdoor_lb_in(arg: &LowBandwidthBuf, res: &mut LowBandwidthBuf); 8 | /// External, low-bandwidth OUT. 9 | fn _vmw_backdoor_lb_out(arg: &LowBandwidthBuf, res: &mut LowBandwidthBuf); 10 | 11 | /// External, high-bandwidth IN. 12 | fn _vmw_backdoor_hb_in(arg: &HighBandwidthBuf, res: &mut HighBandwidthBuf); 13 | /// External, high-bandwidth OUT. 14 | fn _vmw_backdoor_hb_out(arg: &HighBandwidthBuf, res: &mut HighBandwidthBuf); 15 | } 16 | 17 | /// Exchange buffer for low-bandwidth backdoor calls. 18 | #[derive(Default)] 19 | #[repr(C, packed)] 20 | pub(crate) struct LowBandwidthBuf { 21 | pub(crate) eax: u32, 22 | pub(crate) ebx: u32, 23 | pub(crate) ecx: u32, 24 | pub(crate) edx: u32, 25 | pub(crate) ebp: u32, 26 | pub(crate) edi: u32, 27 | pub(crate) esi: u32, 28 | } 29 | 30 | /// Low-bandwidth IN. 31 | pub(crate) fn low_bw_in(arg: &LowBandwidthBuf, res: &mut LowBandwidthBuf) { 32 | trace!( 33 | "lb_in, req: A={:x} - B={:x} - C={:x} - D={:x}", 34 | { arg.eax }, 35 | { arg.ebx }, 36 | { arg.ecx }, 37 | { arg.edx } 38 | ); 39 | 40 | unsafe { _vmw_backdoor_lb_in(arg, res) }; 41 | 42 | trace!( 43 | "lb_in, resp: A={:x} - B={:x} - C={:x} - D={:x}", 44 | { res.eax }, 45 | { res.ebx }, 46 | { res.ecx }, 47 | { res.edx } 48 | ); 49 | } 50 | 51 | pub(crate) fn low_bw_out(arg: &LowBandwidthBuf, res: &mut LowBandwidthBuf) { 52 | trace!( 53 | "lb_out, req: {:x} - {:x} - {:x} - {:x}", 54 | { arg.eax }, 55 | { arg.ebx }, 56 | { arg.ecx }, 57 | { arg.edx } 58 | ); 59 | 60 | unsafe { _vmw_backdoor_lb_out(arg, res) }; 61 | 62 | trace!( 63 | "lb_out, resp: {:x} - {:x} - {:x} - {:x}", 64 | { res.eax }, 65 | { res.ebx }, 66 | { res.ecx }, 67 | { res.edx } 68 | ); 69 | } 70 | 71 | /// Exchange buffer for high-bandwidth backdoor calls. 72 | #[derive(Default)] 73 | #[repr(C, packed)] 74 | pub(crate) struct HighBandwidthBuf { 75 | pub(crate) eax: u32, 76 | pub(crate) ebx: u32, 77 | pub(crate) ecx: u32, 78 | pub(crate) edx: u32, 79 | pub(crate) ebp: u32, 80 | pub(crate) rdi: u64, 81 | pub(crate) rsi: u64, 82 | } 83 | 84 | pub(crate) fn hb_in(arg: &HighBandwidthBuf, res: &mut HighBandwidthBuf) { 85 | trace!( 86 | "hb_in, req: {:x} - {:x} - {:x} - {:x} - {:x}", 87 | { arg.eax }, 88 | { arg.ebx }, 89 | { arg.ecx }, 90 | { arg.edx }, 91 | { arg.rsi } 92 | ); 93 | 94 | unsafe { _vmw_backdoor_hb_in(arg, res) }; 95 | 96 | trace!( 97 | "hb_in, resp: {:x} - {:x} - {:x} - {:x} - {:x}", 98 | { res.eax }, 99 | { res.ebx }, 100 | { res.ecx }, 101 | { res.edx }, 102 | { res.rsi } 103 | ); 104 | } 105 | 106 | pub(crate) fn hb_out(arg: &HighBandwidthBuf, res: &mut HighBandwidthBuf) { 107 | trace!( 108 | "hb_out, req: {:x} - {:x} - {:x} - {:x} - {:x}", 109 | { arg.eax }, 110 | { arg.ebx }, 111 | { arg.ecx }, 112 | { arg.edx }, 113 | { arg.rsi } 114 | ); 115 | 116 | unsafe { _vmw_backdoor_hb_out(arg, res) }; 117 | 118 | trace!( 119 | "hb_out, resp: {:x} - {:x} - {:x} - {:x} - {:x}", 120 | { res.eax }, 121 | { res.ebx }, 122 | { res.ecx }, 123 | { res.edx }, 124 | { res.rsi } 125 | ); 126 | } 127 | 128 | /// Check whether this is running on VMware virtual CPU. 129 | /// 130 | /// [Detection]: 131 | /// * CPUID leaf 0x1 (ECX) contains the virtualization bit. 132 | /// * CPUID leaf 0x4000_0000 (EBX+ECX+EDX) contains the vendor label. 133 | /// 134 | /// [Detection]: https://kb.vmware.com/s/article/1009458 135 | pub fn is_vmware_cpu() -> bool { 136 | use core::arch::x86_64::__cpuid; 137 | 138 | let leaf_1 = unsafe { __cpuid(0x0000_0001) }; 139 | if (leaf_1.ecx & 0x8000_0000) != 0 { 140 | let leaf_vmw = unsafe { __cpuid(0x4000_0000) }; 141 | let mut buf = Vec::with_capacity(12); 142 | buf.extend_from_slice(&leaf_vmw.ebx.to_le_bytes()); 143 | buf.extend_from_slice(&leaf_vmw.ecx.to_le_bytes()); 144 | buf.extend_from_slice(&leaf_vmw.edx.to_le_bytes()); 145 | if buf.as_slice() == b"VMwareVMware" { 146 | return true; 147 | } 148 | } 149 | 150 | false 151 | } 152 | -------------------------------------------------------------------------------- /src/asm/x86_64-linux.s: -------------------------------------------------------------------------------- 1 | .text 2 | 3 | # fn _vmw_backdoor_lb_in(arg: &BackdoorBuf, res: &mut BackdoorBuf); 4 | .global _vmw_backdoor_lb_in 5 | _vmw_backdoor_lb_in: 6 | # Preserve caller values. 7 | movq %rbx, %r8; 8 | movq %rbp, %r9; 9 | movq %rdi, %r10; 10 | movq %rsi, %r11; 11 | 12 | movl 0(%r10), %eax; 13 | movl 4(%r10), %ebx; 14 | movl 8(%r10), %ecx; 15 | movl 12(%r10), %edx; 16 | movl 16(%r10), %ebp; 17 | movl 20(%r10), %edi; 18 | movl 24(%r10), %esi; 19 | 20 | # Backdoor call. 21 | inl %dx, %eax; 22 | 23 | # Record results. 24 | movl %eax, 0(%r11); 25 | movl %ebx, 4(%r11); 26 | movl %ecx, 8(%r11); 27 | movl %edx, 12(%r11); 28 | movl %ebp, 16(%r11); 29 | movl %edi, 20(%r11); 30 | movl %esi, 24(%r11); 31 | 32 | # Restore caller values. 33 | movq %r8, %rbx; 34 | movq %r9, %rbp; 35 | 36 | # Return. 37 | xor %rax, %rax; 38 | ret; 39 | 40 | # fn _vmw_backdoor_lb_out(arg: &BackdoorBuf, res: &mut BackdoorBuf); 41 | .global _vmw_backdoor_lb_out 42 | _vmw_backdoor_lb_out: 43 | # Preserve caller values. 44 | movq %rbx, %r8; 45 | movq %rbp, %r9; 46 | movq %rdi, %r10; 47 | movq %rsi, %r11; 48 | 49 | # Magic number. 50 | movl 0(%r10), %eax; 51 | movl 4(%r10), %ebx; 52 | # Command. 53 | movl 8(%r10), %ecx; 54 | # I/O port (low-bandwidth). 55 | movl 12(%r10), %edx; 56 | movl 16(%r10), %ebp; 57 | movl 20(%r10), %edi; 58 | movl 24(%r10), %esi; 59 | 60 | # Backdoor call. 61 | outl %eax, %dx; 62 | 63 | # Record results. 64 | movl %eax, 0(%r11); 65 | movl %ebx, 4(%r11); 66 | movl %ecx, 8(%r11); 67 | movl %edx, 12(%r11); 68 | movl %ebp, 16(%r11); 69 | movl %edi, 20(%r11); 70 | movl %esi, 24(%r11); 71 | 72 | # Restore caller values. 73 | movq %r8, %rbx; 74 | movq %r9, %rbp; 75 | 76 | # Return. 77 | xor %rax, %rax; 78 | ret; 79 | 80 | # fn _vmw_backdoor_hb_out(arg: &Buf, cmd: &mut Buf); 81 | .global _vmw_backdoor_hb_out 82 | _vmw_backdoor_hb_out: 83 | # Preserve caller values. 84 | movq %rbx, %r8; 85 | movq %rbp, %r9; 86 | movq %rdi, %r10; 87 | movq %rsi, %r11; 88 | 89 | movl 0(%r10), %eax; 90 | movl 4(%r10), %ebx; 91 | movl 8(%r10), %ecx; 92 | movl 12(%r10), %edx; 93 | movl 16(%r10), %ebp; 94 | movq 20(%r10), %rdi; 95 | movq 28(%r10), %rsi; 96 | 97 | # Backdoor call. 98 | cld; 99 | rep; 100 | outsb; 101 | 102 | # Record results. 103 | movl %eax, 0(%r11); 104 | movl %ebx, 4(%r11); 105 | movl %ecx, 8(%r11); 106 | movl %edx, 12(%r11); 107 | movl %ebp, 16(%r11); 108 | movq %rdi, 20(%r11); 109 | movq %rsi, 28(%r11); 110 | 111 | # Restore caller values. 112 | movq %r8, %rbx; 113 | movq %r9, %rbp; 114 | 115 | # Return. 116 | xor %rax, %rax; 117 | ret; 118 | 119 | # fn _vmw_backdoor_hb_in(arg: &Buf, cmd: &mut Buf); 120 | .global _vmw_backdoor_hb_in 121 | _vmw_backdoor_hb_in: 122 | # Preserve caller values. 123 | movq %rbx, %r8; 124 | movq %rbp, %r9; 125 | movq %rdi, %r10; 126 | movq %rsi, %r11; 127 | 128 | movl 0(%r10), %eax; 129 | movl 4(%r10), %ebx; 130 | movl 8(%r10), %ecx; 131 | movl 12(%r10), %edx; 132 | movl 16(%r10), %ebp; 133 | movq 20(%r10), %rdi; 134 | movq 28(%r10), %rsi; 135 | 136 | # Backdoor call. 137 | cld; 138 | rep; 139 | insb; 140 | 141 | # Record results. 142 | movl %eax, 0(%r11); 143 | movl %ebx, 4(%r11); 144 | movl %ecx, 8(%r11); 145 | movl %edx, 12(%r11); 146 | movl %ebp, 16(%r11); 147 | movq %rdi, 20(%r11); 148 | movq %rsi, 28(%r11); 149 | 150 | # Restore caller values. 151 | movq %r8, %rbx; 152 | movq %r9, %rbp; 153 | 154 | # Return. 155 | xor %rax, %rax; 156 | ret; 157 | -------------------------------------------------------------------------------- /src/backdoor.rs: -------------------------------------------------------------------------------- 1 | //! VMware backdoor protocol 2 | //! 3 | //! Ref: https://github.com/vmware/open-vm-tools/blob/stable-11.0.5/open-vm-tools/lib/include/backdoor_def.h 4 | 5 | use crate::{EnhancedChan, VmwError}; 6 | 7 | /// Magic value for all backdoor commands ('VMXh'). 8 | pub(crate) const BACKDOOR_MAGIC: u32 = 0x564D5868; 9 | 10 | /// Low-bandwidth backdoor port. 11 | pub(crate) const BACKDOOR_PORT_LB: u32 = 0x5658; 12 | /// High-bandwidth backdoor port. 13 | pub(crate) const BACKDOOR_PORT_HB: u32 = 0x5659; 14 | 15 | pub(crate) const COMMAND_GET_VERSION: u32 = 0x0A; 16 | pub(crate) const COMMAND_ERPC: u32 = 0x1E; 17 | 18 | /// Try to acquire access to the backdoor, but do NOT probe its presence. 19 | /// 20 | /// On Linux, this tries to change I/O access level via `iopl()`. That requires 21 | /// running with `CAP_SYS_RAWIO` capability and it is not compatible with 22 | /// `kernel_lockdown`. 23 | pub fn access_backdoor_privileged() -> Result { 24 | BackdoorGuard::change_io_access(true)?; 25 | Ok(BackdoorGuard { 26 | release_on_drop: true, 27 | }) 28 | } 29 | 30 | /// Try to acquire access to the backdoor, but do NOT probe its presence. 31 | /// 32 | /// Wherever possible, use `access_backdoor_privileged()` instead. 33 | pub fn access_backdoor() -> Result { 34 | Ok(BackdoorGuard { 35 | release_on_drop: false, 36 | }) 37 | } 38 | 39 | /// Try to acquire access to the backdoor, and probe its presence. 40 | /// 41 | /// On Linux, this tries to change I/O access level via `iopl()`. That requires 42 | /// running with `CAP_SYS_RAWIO` capability and it is not compatible with 43 | /// `kernel_lockdown`. 44 | pub fn probe_backdoor_privileged() -> Result { 45 | BackdoorGuard::change_io_access(true)?; 46 | let mut guard = BackdoorGuard { 47 | release_on_drop: true, 48 | }; 49 | guard.probe_vmware_backdoor()?; 50 | Ok(guard) 51 | } 52 | 53 | /// Try to acquire access to the backdoor, and probe its presence. 54 | /// 55 | /// Wherever possible, use `probe_backdoor_privileged()` instead. 56 | pub fn probe_backdoor() -> Result { 57 | let mut guard = BackdoorGuard { 58 | release_on_drop: false, 59 | }; 60 | guard.probe_vmware_backdoor()?; 61 | Ok(guard) 62 | } 63 | 64 | /// Guard for an open backdoor. 65 | /// 66 | /// This can be acquired via [`access_backdoor`](fn.access_backdoor.html) or 67 | /// [`probe_backdoor`](fn.probe_backdoor.html). 68 | #[derive(Debug)] 69 | pub struct BackdoorGuard { 70 | release_on_drop: bool, 71 | } 72 | 73 | impl BackdoorGuard { 74 | /// Check whether the VMware backdoor is accessible. 75 | pub fn probe_vmware_backdoor(&mut self) -> Result<(), VmwError> { 76 | self.get_version().and(Ok(())) 77 | } 78 | 79 | /// Try to release backdoor access. 80 | pub fn release_access(self) -> Result<(), Self> { 81 | let mut guard = self; 82 | if Self::change_io_access(false).is_err() { 83 | return Err(guard); 84 | } 85 | 86 | guard.release_on_drop = false; 87 | drop(guard); 88 | Ok(()) 89 | } 90 | 91 | /// Open channel for enhanced-RPC. 92 | pub fn open_enhanced_chan(&mut self) -> Result { 93 | EnhancedChan::open(self) 94 | } 95 | 96 | /// Try to change I/O ports access level. 97 | pub(crate) fn change_io_access(acquire: bool) -> Result<(), VmwError> { 98 | // NOTE(lucab): `ioperm()` is not enough here, as the backdoor 99 | // protocol uses a dynamic range of I/O ports. 100 | let level = if acquire { 0b11 } else { 0b00 }; 101 | let err = unsafe { libc::iopl(level) }; 102 | if err != 0 { 103 | let err_code = errno::errno(); 104 | return Err(format!("iopl failed: {} (errno: {})", err_code, err_code.0).into()); 105 | }; 106 | 107 | Ok(()) 108 | } 109 | } 110 | 111 | impl Drop for BackdoorGuard { 112 | fn drop(&mut self) { 113 | if self.release_on_drop { 114 | if let Err(e) = Self::change_io_access(false) { 115 | log::error!("failed to release backdoor access: {}", e); 116 | } 117 | } 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use super::*; 124 | 125 | #[test] 126 | fn test_magic_string() { 127 | assert_eq!(BACKDOOR_MAGIC, u32::from_be_bytes(*b"VMXh")); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/erpc.rs: -------------------------------------------------------------------------------- 1 | //! Enhanced-RPC protocol (ERPC). 2 | //! 3 | //! Ref: https://sites.google.com/site/chitchatvmback/backdoor 4 | 5 | use crate::{asm, backdoor}; 6 | use crate::{BackdoorGuard, VmwError}; 7 | 8 | /// Magic value for all enhanced-RPC commands ('RPCI'). 9 | const RPCI_MAGIC: u32 = 0xC9435052; 10 | 11 | /// Magic value for all enhanced-RPC data transfers. 12 | const ERPC_DATA_MAGIC: u32 = 0x0001_0000; 13 | 14 | /// ERPC subcommand: open. 15 | const ERPC_OPEN: u32 = 0x0; 16 | /// ERPC subcommand: send command length. 17 | const ERPC_SEND_CMD_LEN: u32 = 0x1; 18 | /// ERPC subcommand: receive data length. 19 | const ERPC_RECV_REPLY_LEN: u32 = 0x3; 20 | /// ERPC subcommand: clone. 21 | const ERPC_CLOSE: u32 = 0x6; 22 | 23 | /// Channel for enhanced-RPC transfers. 24 | /// 25 | /// This can be acquired via [`BackdoorGuard::open_enhanced_chan`](struct.BackdoorGuard.html#method.open_enhanced_chan). 26 | #[derive(Debug)] 27 | pub struct EnhancedChan<'a> { 28 | _guard: &'a mut BackdoorGuard, 29 | pub(crate) chan_id: u32, 30 | pub(crate) cookie1: u32, 31 | pub(crate) cookie2: u32, 32 | } 33 | 34 | impl<'a> Drop for EnhancedChan<'a> { 35 | fn drop(&mut self) { 36 | if self.close_channel().is_err() { 37 | log::warn!("failed to close enhanced-RPC channel"); 38 | }; 39 | } 40 | } 41 | 42 | impl<'a> EnhancedChan<'a> { 43 | /// Open an enhanced-RPC channel. 44 | pub fn open(guard: &'a mut BackdoorGuard) -> Result { 45 | let arg = asm::LowBandwidthBuf { 46 | eax: backdoor::BACKDOOR_MAGIC, 47 | ebx: RPCI_MAGIC, 48 | ecx: erpc_subcommand(ERPC_OPEN), 49 | edx: backdoor::BACKDOOR_PORT_LB, 50 | ..Default::default() 51 | }; 52 | let mut res = asm::LowBandwidthBuf::default(); 53 | asm::low_bw_out(&arg, &mut res); 54 | if res.ecx == 0 { 55 | return Err("erpc open failed".into()); 56 | } 57 | 58 | let ch = EnhancedChan { 59 | _guard: guard, 60 | chan_id: res.edx, 61 | cookie1: res.esi, 62 | cookie2: res.edi, 63 | }; 64 | 65 | Ok(ch) 66 | } 67 | 68 | /// Close the channel. 69 | /// 70 | /// On error, this returns back the channel. 71 | pub fn close(self) -> Result<(), Self> { 72 | let mut erpc = self; 73 | match erpc.close_channel() { 74 | Ok(_) => Ok(()), 75 | Err(_) => Err(erpc), 76 | } 77 | } 78 | 79 | fn close_channel(&mut self) -> Result<(), VmwError> { 80 | let arg = asm::LowBandwidthBuf { 81 | eax: backdoor::BACKDOOR_MAGIC, 82 | ebx: 0, 83 | ecx: erpc_subcommand(ERPC_CLOSE), 84 | edx: erpc_cmd_channel(self.chan_id), 85 | esi: self.cookie1, 86 | edi: self.cookie2, 87 | ebp: 0, 88 | }; 89 | let mut res = asm::LowBandwidthBuf::default(); 90 | asm::low_bw_out(&arg, &mut res); 91 | if res.ecx == 0 { 92 | return Err("erpc close_channel failed".into()); 93 | } 94 | 95 | Ok(()) 96 | } 97 | 98 | fn send_command_len(&mut self, cmd_len: u32) -> Result<(), VmwError> { 99 | let arg = asm::LowBandwidthBuf { 100 | eax: backdoor::BACKDOOR_MAGIC, 101 | ebx: cmd_len, 102 | ecx: erpc_subcommand(ERPC_SEND_CMD_LEN), 103 | edx: erpc_cmd_channel(self.chan_id), 104 | esi: self.cookie1, 105 | edi: self.cookie2, 106 | ebp: 0, 107 | }; 108 | let mut res = asm::LowBandwidthBuf::default(); 109 | asm::low_bw_out(&arg, &mut res); 110 | if res.ecx == 0 { 111 | return Err("erpc send_command_len failed".into()); 112 | } 113 | 114 | Ok(()) 115 | } 116 | 117 | fn send_command_data(&mut self, command: &[u8], cmd_len: u32) -> Result<(), VmwError> { 118 | let arg = asm::HighBandwidthBuf { 119 | eax: backdoor::BACKDOOR_MAGIC, 120 | ebx: ERPC_DATA_MAGIC, 121 | ecx: cmd_len, 122 | edx: erpc_data_channel(self.chan_id), 123 | ebp: self.cookie1, 124 | rdi: self.cookie2 as u64, 125 | rsi: command.as_ptr() as u64, 126 | }; 127 | let mut res = asm::HighBandwidthBuf::default(); 128 | asm::hb_out(&arg, &mut res); 129 | if res.ebx == 0 { 130 | return Err("erpc send_command_len failed".into()); 131 | } 132 | 133 | Ok(()) 134 | } 135 | 136 | fn recv_reply_len(&mut self) -> Result { 137 | let arg = asm::LowBandwidthBuf { 138 | eax: backdoor::BACKDOOR_MAGIC, 139 | ebx: 0, 140 | ecx: erpc_subcommand(ERPC_RECV_REPLY_LEN), 141 | edx: erpc_cmd_channel(self.chan_id), 142 | esi: self.cookie1, 143 | edi: self.cookie2, 144 | ebp: 0, 145 | }; 146 | let mut res = asm::LowBandwidthBuf::default(); 147 | asm::low_bw_out(&arg, &mut res); 148 | if res.ecx == 0 { 149 | return Err("erpc recv_reply_len failed".into()); 150 | } 151 | 152 | Ok(res.ebx as usize) 153 | } 154 | 155 | fn recv_reply_data(&mut self, reply_len: usize) -> Result, VmwError> { 156 | let buf = vec![0; reply_len]; 157 | let arg = asm::HighBandwidthBuf { 158 | eax: backdoor::BACKDOOR_MAGIC, 159 | ebx: ERPC_DATA_MAGIC, 160 | ecx: reply_len as u32, 161 | edx: erpc_data_channel(self.chan_id), 162 | ebp: self.cookie2, 163 | rdi: buf.as_slice().as_ptr() as u64, 164 | rsi: self.cookie1 as u64, 165 | }; 166 | let mut res = asm::HighBandwidthBuf::default(); 167 | asm::hb_in(&arg, &mut res); 168 | if res.ebx == 0 { 169 | return Err("erpc recv_reply_data failed".into()); 170 | } 171 | 172 | Ok(buf) 173 | } 174 | 175 | /// Retrieve a guestinfo property. 176 | pub fn get_guestinfo(&mut self, key: &[u8]) -> Result>, VmwError> { 177 | let mut command = b"info-get ".to_vec(); 178 | command.extend(key); 179 | command.push(b'\0'); 180 | let cmd_len = command.len() as u32; 181 | self.send_command_len(cmd_len)?; 182 | self.send_command_data(&command, cmd_len)?; 183 | let len = self.recv_reply_len()?; 184 | let mut buf = self.recv_reply_data(len)?; 185 | 186 | if buf.len() < 2 { 187 | return Err(format!("reply too short ({} bytes)", buf.len()).into()); 188 | } 189 | 190 | if buf.remove(0) == b'0' { 191 | Ok(None) 192 | } else { 193 | // Strip whitespace separator. 194 | buf.remove(0); 195 | Ok(Some(buf)) 196 | } 197 | } 198 | 199 | /// Log a message. 200 | pub fn log(&mut self, msg: &str) -> Result<(), VmwError> { 201 | let mut command = b"log ".to_vec(); 202 | command.extend(msg.as_bytes()); 203 | command.push(b'\0'); 204 | let cmd_len = command.len() as u32; 205 | self.send_command_len(cmd_len)?; 206 | self.send_command_data(&command, cmd_len)?; 207 | let len = self.recv_reply_len()?; 208 | let buf = self.recv_reply_data(len)?; 209 | 210 | if buf.is_empty() { 211 | return Err("empty reply".into()); 212 | } 213 | 214 | match buf[0] { 215 | b'1' => Ok(()), 216 | _ => Err("erpc log failed".into()), 217 | } 218 | } 219 | 220 | /// Report agent (unmanaged) type. 221 | pub fn report_agent(&mut self) -> Result<(), VmwError> { 222 | const TOOLS_VERSION_UNMANAGED: i32 = std::i32::MAX; 223 | let mut command = format!("tools.set.version {}", TOOLS_VERSION_UNMANAGED) 224 | .as_bytes() 225 | .to_vec(); 226 | command.push(b'\0'); 227 | let cmd_len = command.len() as u32; 228 | self.send_command_len(cmd_len)?; 229 | self.send_command_data(&command, cmd_len)?; 230 | let len = self.recv_reply_len()?; 231 | let buf = self.recv_reply_data(len)?; 232 | 233 | if buf.is_empty() { 234 | return Err("empty reply".into()); 235 | } 236 | 237 | match buf[0] { 238 | b'1' => Ok(()), 239 | _ => Err("erpc tools.set.version failed".into()), 240 | } 241 | } 242 | } 243 | 244 | /// Format an ERPC subcommand. 245 | fn erpc_subcommand(subcmd: u32) -> u32 { 246 | (subcmd << 16) | backdoor::COMMAND_ERPC 247 | } 248 | 249 | /// Format an ERPC channel for control commands. 250 | fn erpc_cmd_channel(chan_id: u32) -> u32 { 251 | (chan_id & 0xffff_0000) | backdoor::BACKDOOR_PORT_LB 252 | } 253 | 254 | /// Format an ERPC channel for data tranfer. 255 | fn erpc_data_channel(chan_id: u32) -> u32 { 256 | (chan_id & 0xffff_0000) | backdoor::BACKDOOR_PORT_HB 257 | } 258 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// VMware backdoor errors. 4 | #[derive(Error, Debug)] 5 | #[error("vmware backdoor error: {0}")] 6 | pub struct VmwError(pub(crate) String); 7 | 8 | impl From<&str> for VmwError { 9 | fn from(arg: &str) -> Self { 10 | VmwError(arg.to_string()) 11 | } 12 | } 13 | 14 | impl From for VmwError { 15 | fn from(arg: String) -> Self { 16 | VmwError(arg) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A pure-Rust library for VMware host-guest protocol ("VMXh backdoor"). 2 | //! 3 | //! This library provides helpers to access and use the VMware backdoor from a 4 | //! guest VM. It allows bi-directional interactions with the VMWare hypervisor 5 | //! and host environment. 6 | //! 7 | //! The "backdoor" protocol does not have official specifications, but it has been 8 | //! widely analyzed and there are multiple projects documenting it: 9 | //! 1. [https://github.com/vmware/open-vm-tools/blob/stable-11.0.5/open-vm-tools/lib/include/backdoor_def.h][1] 10 | //! 2. [https://wiki.osdev.org/VMware_tools][2] 11 | //! 3. [https://sysprogs.com/legacy/articles/kdvmware/guestrpc.shtml][3] 12 | //! 4. [https://github.com/vmware/vmw-guestinfo/tree/master/bdoor][4] 13 | //! 5. [https://sites.google.com/site/chitchatvmback/backdoor][5] 14 | //! 15 | //! [1]: https://github.com/vmware/open-vm-tools/blob/stable-11.0.5/open-vm-tools/lib/include/backdoor_def.h 16 | //! [2]: https://wiki.osdev.org/VMware_tools 17 | //! [3]: https://sysprogs.com/legacy/articles/kdvmware/guestrpc.shtml 18 | //! [4]: https://github.com/vmware/vmw-guestinfo/tree/master/bdoor 19 | //! [5]: https://sites.google.com/site/chitchatvmback/backdoor 20 | //! 21 | //! ## Example 22 | //! 23 | //! ```rust,ignore 24 | //! let is_vmw = vmw_backdoor::is_vmware_cpu(); 25 | //! println!("VMware CPU detected: {}.", is_vmw); 26 | //! 27 | //! let mut guard = vmw_backdoor::access_backdoor_privileged().unwrap(); 28 | //! println!("Raised I/O access to reach backdoor port."); 29 | //! 30 | //! let found = guard.probe_vmware_backdoor().unwrap_or(false); 31 | //! println!("VMware backdoor detected: {}.", found); 32 | //! 33 | //! let mut erpc = guard.open_enhanced_chan().unwrap(); 34 | //! let key = "guestinfo.ignition.config.data"; 35 | //! let guestinfo = erpc.get_guestinfo(key.as_bytes()).unwrap(); 36 | //! 37 | //! if let Some(val) = guestinfo { 38 | //! println!("Got value for key '{}':", key); 39 | //! println!("{}", String::from_utf8_lossy(&val)); 40 | //! }; 41 | //! ``` 42 | 43 | #![deny(missing_debug_implementations)] 44 | #![deny(missing_docs)] 45 | #![allow(clippy::unreadable_literal)] 46 | 47 | mod error; 48 | pub use error::VmwError; 49 | 50 | cfg_if::cfg_if! { 51 | if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { 52 | mod asm; 53 | mod backdoor; 54 | mod erpc; 55 | mod low_bw; 56 | 57 | pub use asm::is_vmware_cpu; 58 | pub use backdoor::{ 59 | access_backdoor, access_backdoor_privileged, probe_backdoor, probe_backdoor_privileged, 60 | BackdoorGuard, 61 | }; 62 | pub use erpc::EnhancedChan; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/low_bw.rs: -------------------------------------------------------------------------------- 1 | /// Low-bandwidth backdoor protocol. 2 | use crate::{asm, backdoor}; 3 | use crate::{BackdoorGuard, VmwError}; 4 | 5 | impl BackdoorGuard { 6 | pub(crate) fn get_version(&mut self) -> Result { 7 | let arg = asm::LowBandwidthBuf { 8 | eax: backdoor::BACKDOOR_MAGIC, 9 | ebx: 0, 10 | ecx: backdoor::COMMAND_GET_VERSION, 11 | edx: backdoor::BACKDOOR_PORT_LB, 12 | ebp: 0, 13 | edi: 0, 14 | esi: 0, 15 | }; 16 | let mut res = asm::LowBandwidthBuf::default(); 17 | asm::low_bw_in(&arg, &mut res); 18 | if res.ebx != backdoor::BACKDOOR_MAGIC { 19 | return Err("get_version failed".into()); 20 | } 21 | Ok(res.ecx) 22 | } 23 | } 24 | --------------------------------------------------------------------------------