mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea3e2ff625 | ||
|
|
3320015793 | ||
|
|
9113544e21 | ||
|
|
8c526891ac | ||
|
|
78a30012fb | ||
|
|
c5c751c478 | ||
|
|
f92cce888b | ||
|
|
00b4d97826 | ||
|
|
3fe7d6d904 | ||
|
|
02f146fef8 | ||
|
|
36cdd67b9c | ||
|
|
92b12ce2f4 | ||
|
|
cbb588c380 | ||
|
|
1c7997c5c3 | ||
|
|
fee769bed4 | ||
|
|
6b2c2c938a | ||
|
|
1e967697c2 | ||
|
|
959ea7115f | ||
|
|
398e9338a7 | ||
|
|
5c4b5c0e40 | ||
|
|
f9bd2aaf9f | ||
|
|
f126288159 | ||
|
|
9f9a4e657f | ||
|
|
3097d78c6e | ||
|
|
7c2a252c02 | ||
|
|
a87b25a962 | ||
|
|
24ed063dbe | ||
|
|
fbac81843f | ||
|
|
610a2234dc | ||
|
|
5f18ac3bc0 | ||
|
|
4bf2d4f7dd | ||
|
|
954f3dd057 | ||
|
|
026e2c6aa8 | ||
|
|
be9f57dcf9 | ||
|
|
bd180bc3eb | ||
|
|
b9f086bcb3 | ||
|
|
d2f0e8aebb | ||
|
|
513973873c | ||
|
|
4c52fa344e | ||
|
|
e18d32bac2 | ||
|
|
59185050d5 | ||
|
|
dc72ef42dc | ||
|
|
68bb32a3e7 | ||
|
|
0f89387475 | ||
|
|
f5b2eb5c76 | ||
|
|
82ac114b8a | ||
|
|
2ffd5df018 | ||
|
|
006a12d911 | ||
|
|
bb55976454 | ||
|
|
a15e041138 | ||
|
|
0fa96402b8 | ||
|
|
bc0ef1d8ba | ||
|
|
86153225cf | ||
|
|
ddc3f194aa | ||
|
|
c6cc3d3ba9 | ||
|
|
0fbb3e2c2c | ||
|
|
330e1aac36 | ||
|
|
5fddf76a3b | ||
|
|
b50dfb3538 | ||
|
|
502995746a | ||
|
|
8aa2d7ab2a | ||
|
|
bd8c28d341 | ||
|
|
422906a0c7 | ||
|
|
b7512263be | ||
|
|
7e749c0390 | ||
|
|
61a5bae25c | ||
|
|
a2438700e4 | ||
|
|
bb4af57219 | ||
|
|
824e83ab0d | ||
|
|
29675cfe68 | ||
|
|
43632f255b | ||
|
|
a1941ad10b | ||
|
|
70db454b90 | ||
|
|
7b3ffd89a5 | ||
|
|
c1f18fa634 | ||
|
|
efb92d409f | ||
|
|
e4aa3617c7 | ||
|
|
d7e8cd8258 | ||
|
|
8803e4a2e7 | ||
|
|
d2a01f7cec | ||
|
|
dec4a82058 | ||
|
|
5fc143205c | ||
|
|
b73d94f969 | ||
|
|
ead4f767ff | ||
|
|
bc6acb842f | ||
|
|
d89f1f4f21 | ||
|
|
dc90744ec3 | ||
|
|
18340d9761 | ||
|
|
cfaa192eb4 | ||
|
|
c67f799717 | ||
|
|
b47efc3bc2 | ||
|
|
498f35a1ab | ||
|
|
2d61bc36ed | ||
|
|
46447962b8 | ||
|
|
9a6bc025a7 | ||
|
|
de1abe0e30 | ||
|
|
e97cee99ef | ||
|
|
3103526153 | ||
|
|
08ff89ba6e | ||
|
|
514f36fb5a | ||
|
|
fe3506976e | ||
|
|
94929889ac | ||
|
|
8452edfcc7 | ||
|
|
466cc5e043 | ||
|
|
034c674c4c | ||
|
|
b785e00317 | ||
|
|
7f6731cd05 | ||
|
|
457fd9ef96 | ||
|
|
eea45a2c78 | ||
|
|
11c17ca319 | ||
|
|
249e362c70 | ||
|
|
b3e5738b40 | ||
|
|
1dd53c3d24 | ||
|
|
130c988380 | ||
|
|
8c76a3e4b5 | ||
|
|
af5a9677c8 | ||
|
|
1d89f98edf | ||
|
|
287dbd4fc9 | ||
|
|
c50ea2c708 | ||
|
|
14b45e4d2e | ||
|
|
519476e842 | ||
|
|
5fec858130 | ||
|
|
67f22b6aa4 | ||
|
|
1eb4133034 | ||
|
|
9f3bea3e3f | ||
|
|
e7c7a304e8 | ||
|
|
b5cce0d371 | ||
|
|
7c608f6380 | ||
|
|
3ad8d24943 | ||
|
|
56c6c02724 | ||
|
|
19b0ea842b | ||
|
|
16bd88b4b5 | ||
|
|
09136c50b5 | ||
|
|
380b7d398c | ||
|
|
764959c9a8 | ||
|
|
7d84654a79 | ||
|
|
246cb94dc5 | ||
|
|
165c2c476f | ||
|
|
5e8f380698 |
3
.github/workflows/build.yaml
vendored
3
.github/workflows/build.yaml
vendored
@@ -49,6 +49,9 @@ jobs:
|
||||
- name: Build (default features + guest_debug)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + pvmemcontrol)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (mshv)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
|
||||
7
.github/workflows/quality.yaml
vendored
7
.github/workflows/quality.yaml
vendored
@@ -80,6 +80,13 @@ jobs:
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + pvmemcontrol)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + tracing)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
254
Cargo.lock
generated
254
Cargo.lock
generated
@@ -12,9 +12,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
@@ -36,23 +36,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.13"
|
||||
version = "0.6.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
@@ -74,9 +75,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -84,9 +85,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.81"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "api_client"
|
||||
@@ -136,9 +137,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
|
||||
checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener 5.3.0",
|
||||
@@ -281,9 +282,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.71"
|
||||
version = "0.3.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
||||
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
@@ -296,9 +297,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bitfield-struct"
|
||||
version = "0.5.6"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26b8cea8bb6a81b75a84603b9e096f05fa86db057904ef29be1deee900532bd"
|
||||
checksum = "6c2ce686adbebce0ee484a502c440b4657739adbad65eadf06d64f5816ee9765"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -313,9 +314,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
@@ -362,9 +363,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.4"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -420,7 +421,7 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor"
|
||||
version = "40.0.0"
|
||||
version = "41.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api_client",
|
||||
@@ -451,9 +452,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
@@ -509,9 +510,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -519,9 +520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
@@ -533,9 +534,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
@@ -566,12 +567,13 @@ dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arch",
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"num_enum",
|
||||
"pci",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -638,9 +640,9 @@ checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.9"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d"
|
||||
checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
@@ -648,9 +650,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.9"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
|
||||
checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -686,7 +688,7 @@ version = "4.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -698,9 +700,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -897,7 +899,7 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6341b3480afbb34eaefc7f92713bc92f2d83e338aaa1c44192f9c2956f4a4903"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"managed",
|
||||
@@ -940,9 +942,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
@@ -1015,9 +1017,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "igvm"
|
||||
version = "0.3.1"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bc8970c7e36437c52af3b3ef1acaa5e334c2a95cd8ee9639d574830f48af17e"
|
||||
checksum = "7984b10433b50e06a06bd50c69bca4888a5d7de8975f64ea4c2a7687eb99b09d"
|
||||
dependencies = [
|
||||
"bitfield-struct",
|
||||
"crc32fast",
|
||||
@@ -1032,9 +1034,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "igvm_defs"
|
||||
version = "0.3.1"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c4942827cef415726296f6d62411afdb13c1b1924125f3222988f04bef33ad2"
|
||||
checksum = "b64ec5588c475372ae830475d3ee9a7bd255407dcb9f03faf6d493556eb6105a"
|
||||
dependencies = [
|
||||
"bitfield-struct",
|
||||
"open-enum",
|
||||
@@ -1080,6 +1082,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@@ -1112,12 +1120,23 @@ version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bedae2ca4a531bebe311abaf9691f5cc14eaa21475243caa2e39c43bb872947d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "landlock"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dafb8a4afee64f167eb2b52d32f0eea002e41a7a6450e68c799c8ec3a81a634c"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -1126,9 +1145,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
@@ -1136,7 +1155,7 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1156,9 +1175,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.16"
|
||||
version = "1.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9"
|
||||
checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -1193,9 +1212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
@@ -1311,7 +1330,7 @@ version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -1335,19 +1354,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
@@ -1355,9 +1375,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -1370,29 +1390,29 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "open-enum"
|
||||
version = "0.4.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba485b94b3e73fa752d98cfcab74647a4a537269682cc1ee5256aa020432506d"
|
||||
checksum = "2eb2508143a400b3361812094d987dd5adc81f0f5294a46491be648d6c94cab5"
|
||||
dependencies = [
|
||||
"open-enum-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "open-enum-derive"
|
||||
version = "0.4.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1c261430059cab8b2b51eec42a3c15750439ec6c013cd8fe41d4a450de776"
|
||||
checksum = "8d1296fab5231654a5aec8bf9e87ba4e3938c502fc4c3c0425a00084c78944be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.2.3+3.2.1"
|
||||
version = "300.3.1+3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843"
|
||||
checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -1486,9 +1506,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pci"
|
||||
@@ -1538,9 +1558,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
|
||||
checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
@@ -1555,9 +1575,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "pnet"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "130c5b738eeda2dc5796fe2671e49027e6935e817ab51b930a36ec9e6a206a64"
|
||||
checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"pnet_base",
|
||||
@@ -1569,18 +1589,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_base"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c"
|
||||
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
|
||||
dependencies = [
|
||||
"no-std-net",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_datalink"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad5854abf0067ebbd3967f7d45ebc8976ff577ff0c7bd101c4973ae3c70f98fe"
|
||||
checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"libc",
|
||||
@@ -1591,9 +1611,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804"
|
||||
checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1603,18 +1623,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros_support"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56"
|
||||
checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab"
|
||||
dependencies = [
|
||||
"pnet_base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_packet"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba"
|
||||
checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"pnet_base",
|
||||
@@ -1624,9 +1644,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_sys"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417c0becd1b573f6d544f73671070b039051e5ad819cc64aa96377b536128d00"
|
||||
checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
@@ -1634,9 +1654,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_transport"
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2637e14d7de974ee2f74393afccbc8704f3e54e6eb31488715e72481d1662cc3"
|
||||
checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pnet_base",
|
||||
@@ -1661,9 +1681,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
@@ -1770,9 +1793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -1799,9 +1822,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "remain"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad9f2390298a947ee0aa6073d440e221c0726188cfbcdf9604addb6ee393eb4a"
|
||||
checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1826,7 +1849,7 @@ version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -1876,9 +1899,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.115"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -1898,9 +1921,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.7.0"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
|
||||
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -1909,9 +1932,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.7.0"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
|
||||
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -1946,9 +1969,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -2062,18 +2085,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2257,7 +2280,7 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6be08d1166d41a78861ad50212ab3f9eca0729c349ac3a7a8f557c62406b87cc"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -2433,7 +2456,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"arch",
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
@@ -2450,6 +2473,7 @@ dependencies = [
|
||||
"hypervisor",
|
||||
"igvm",
|
||||
"igvm_defs",
|
||||
"landlock",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log",
|
||||
@@ -2803,9 +2827,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
@@ -2813,9 +2837,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.32"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2824,9 +2848,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.1.1"
|
||||
version = "4.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa6d31a02fbfb602bfde791de7fedeb9c2c18115b3d00f3a36e489f46ffbbc7"
|
||||
checksum = "1724a2b330760dc7d2a8402d841119dc869ef120b139d29862d6980e9c75bfc9"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
@@ -2837,9 +2861,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.1.1"
|
||||
version = "4.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "642bf1b6b6d527988b3e8193d20969d53700a36eac734d21ae6639db168701c8"
|
||||
checksum = "55025a7a518ad14518fb243559c058a2e5b848b015e31f1d90414f36e3317859"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -5,9 +5,9 @@ default-run = "cloud-hypervisor"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "40.0.0"
|
||||
version = "41.0.0"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
@@ -29,20 +29,20 @@ inherits = "release"
|
||||
strip = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "4.5.4", features = ["string"] }
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
epoll = "4.3.3"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.153"
|
||||
log = { version = "0.4.21", features = ["std"] }
|
||||
libc = "0.2.155"
|
||||
log = { version = "0.4.22", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = "0.4.0"
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
signal-hook = "0.3.17"
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
tpm = { path = "tpm" }
|
||||
tracer = { path = "tracer" }
|
||||
vm-memory = "0.14.1"
|
||||
@@ -54,7 +54,7 @@ zbus = { version = "4.1.2", optional = true }
|
||||
dirs = "5.0.1"
|
||||
net_util = { path = "net_util" }
|
||||
once_cell = "1.19.0"
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
@@ -69,6 +69,7 @@ igvm = ["mshv", "vmm/igvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
||||
tdx = ["vmm/tdx"]
|
||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
||||
|
||||
@@ -5,5 +5,5 @@ name = "api_client"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.61"
|
||||
thiserror = "1.0.62"
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -6,18 +6,19 @@ version = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
kvm = []
|
||||
sev_snp = []
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
byteorder = "1.5.0"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.21"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
uuid = "1.8.0"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-bitmap",
|
||||
|
||||
@@ -34,6 +34,7 @@ use std::arch::x86_64;
|
||||
pub mod tdx;
|
||||
|
||||
// CPUID feature bits
|
||||
#[cfg(feature = "kvm")]
|
||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
||||
@@ -619,17 +620,8 @@ pub fn generate_common_cpuid(
|
||||
"Generating guest CPUID for with physical address size: {}",
|
||||
config.phys_bits
|
||||
);
|
||||
let cpuid_patches = vec![
|
||||
// Patch tsc deadline timer bit
|
||||
CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
},
|
||||
#[allow(unused_mut)]
|
||||
let mut cpuid_patches = vec![
|
||||
// Patch hypervisor bit
|
||||
CpuidPatch {
|
||||
function: 1,
|
||||
@@ -652,6 +644,23 @@ pub fn generate_common_cpuid(
|
||||
},
|
||||
];
|
||||
|
||||
#[cfg(feature = "kvm")]
|
||||
if matches!(
|
||||
hypervisor.hypervisor_type(),
|
||||
hypervisor::HypervisorType::Kvm
|
||||
) {
|
||||
// Patch tsc deadline timer bit
|
||||
cpuid_patches.push(CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Supported CPUID
|
||||
let mut cpuid = hypervisor
|
||||
.get_supported_cpuid()
|
||||
|
||||
@@ -12,12 +12,12 @@ io_uring = ["dep:io-uring"]
|
||||
byteorder = "1.5.0"
|
||||
crc-any = "2.4.4"
|
||||
io-uring = { version = "0.6.3", optional = true }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
remain = "0.2.13"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
remain = "0.2.14"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
smallvec = "1.13.2"
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
virtio-bindings = { version = "0.2.2", features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = "0.12.0"
|
||||
|
||||
@@ -164,8 +164,8 @@ pub enum ExecuteError {
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
pub fn status(&self) -> u32 {
|
||||
match *self {
|
||||
pub fn status(&self) -> u8 {
|
||||
let status = match *self {
|
||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||
@@ -180,7 +180,8 @@ impl ExecuteError {
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||
}
|
||||
};
|
||||
status as u8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,9 +248,8 @@ impl Request {
|
||||
let hdr_desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("Missing head descriptor");
|
||||
e
|
||||
})?;
|
||||
|
||||
// The head contains the request type which MUST be readable.
|
||||
@@ -275,9 +275,8 @@ impl Request {
|
||||
let mut desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("Only head descriptor present: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
|
||||
if !desc.has_next() {
|
||||
@@ -308,9 +307,8 @@ impl Request {
|
||||
desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
status_desc = desc;
|
||||
|
||||
@@ -6,21 +6,26 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "2.5.0"
|
||||
bitflags = "2.6.0"
|
||||
byteorder = "1.5.0"
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
num_enum = "0.7.2"
|
||||
pci = { path = "../pci" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
tpm = { path = "../tpm" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.14.1"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -29,3 +34,4 @@ arch = { path = "../arch" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pvmemcontrol = []
|
||||
|
||||
@@ -411,14 +411,11 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
|
||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
assert_eq!(
|
||||
serial_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'a', b'b', b'c']
|
||||
);
|
||||
serial.write(0, DATA as u64, b"xy");
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, b"b");
|
||||
serial.write(0, DATA as u64, b"c");
|
||||
assert_eq!(serial_out.buf.lock().unwrap().as_slice(), b"abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -436,7 +433,7 @@ mod tests {
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
serial.queue_input_bytes(b"abc").unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
@@ -473,7 +470,7 @@ mod tests {
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
let mut data = [0u8];
|
||||
@@ -515,9 +512,9 @@ mod tests {
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, b"b");
|
||||
serial.write(0, DATA as u64, b"c");
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, MSR as u64, &mut data[..]);
|
||||
|
||||
@@ -524,14 +524,11 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
|
||||
pl011.write(0, UARTDR, &[b'x', b'y']);
|
||||
pl011.write(0, UARTDR, &[b'a']);
|
||||
pl011.write(0, UARTDR, &[b'b']);
|
||||
pl011.write(0, UARTDR, &[b'c']);
|
||||
assert_eq!(
|
||||
pl011_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'x', b'a', b'b', b'c']
|
||||
);
|
||||
pl011.write(0, UARTDR, b"xy");
|
||||
pl011.write(0, UARTDR, b"a");
|
||||
pl011.write(0, UARTDR, b"b");
|
||||
pl011.write(0, UARTDR, b"c");
|
||||
assert_eq!(pl011_out.buf.lock().unwrap().as_slice(), b"xabc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -549,7 +546,7 @@ mod tests {
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
pl011.queue_input_bytes(b"abc").unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pub mod pvmemcontrol;
|
||||
pub mod pvpanic;
|
||||
pub mod tpm;
|
||||
|
||||
|
||||
819
devices/src/pvmemcontrol.rs
Normal file
819
devices/src/pvmemcontrol.rs
Normal file
@@ -0,0 +1,819 @@
|
||||
// Copyright © 2024 Google LLC
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use num_enum::TryFromPrimitive;
|
||||
use pci::{
|
||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CString,
|
||||
io, result,
|
||||
sync::{Arc, Barrier, Mutex, RwLock},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use vm_allocator::{page_size::get_page_size, AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDeviceSync, Resource};
|
||||
use vm_memory::{
|
||||
bitmap::AtomicBitmap, Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory,
|
||||
GuestMemoryAtomic, GuestMemoryError, GuestMemoryMmap, Le32, Le64,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
const PVMEMCONTROL_VENDOR_ID: u16 = 0x1ae0;
|
||||
const PVMEMCONTROL_DEVICE_ID: u16 = 0x0087;
|
||||
|
||||
const PVMEMCONTROL_SUBSYSTEM_VENDOR_ID: u16 = 0x1ae0;
|
||||
const PVMEMCONTROL_SUBSYSTEM_ID: u16 = 0x011F;
|
||||
|
||||
const MAJOR_VERSION: u64 = 1;
|
||||
const MINOR_VERSION: u64 = 0;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
// device errors
|
||||
#[error("Guest gave us bad memory addresses: {0}")]
|
||||
GuestMemory(#[source] GuestMemoryError),
|
||||
#[error("Guest sent us invalid request")]
|
||||
InvalidRequest,
|
||||
|
||||
#[error("Guest sent us invalid command: {0}")]
|
||||
InvalidCommand(u32),
|
||||
#[error("Guest sent us invalid connection: {0}")]
|
||||
InvalidConnection(u32),
|
||||
|
||||
// pvmemcontrol errors
|
||||
#[error("Request contains invalid arguments: {0}")]
|
||||
InvalidArgument(u64),
|
||||
#[error("Unknown function code: {0}")]
|
||||
UnknownFunctionCode(u64),
|
||||
#[error("Libc call fail: {0}")]
|
||||
LibcFail(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum PvmemcontrolSubclass {
|
||||
Other = 0x80,
|
||||
}
|
||||
|
||||
impl PciSubclass for PvmemcontrolSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// commands have 0 as the most significant byte
|
||||
#[repr(u32)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, TryFromPrimitive)]
|
||||
enum PvmemcontrolTransportCommand {
|
||||
Reset = 0x060f_e6d2,
|
||||
Register = 0x0e35_9539,
|
||||
Ready = 0x0ca8_d227,
|
||||
Disconnect = 0x030f_5da0,
|
||||
Ack = 0x03cf_5196,
|
||||
Error = 0x01fb_a249,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransportRegister {
|
||||
buf_phys_addr: Le64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransportRegisterResponse {
|
||||
command: Le32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
union PvmemcontrolTransportUnion {
|
||||
register: PvmemcontrolTransportRegister,
|
||||
register_response: PvmemcontrolTransportRegisterResponse,
|
||||
unit: (),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion,
|
||||
command: PvmemcontrolTransportCommand,
|
||||
}
|
||||
|
||||
const PVMEMCONTROL_DEVICE_MMIO_SIZE: u64 = std::mem::size_of::<PvmemcontrolTransport>() as u64;
|
||||
const PVMEMCONTROL_DEVICE_MMIO_ALIGN: u64 = std::mem::align_of::<PvmemcontrolTransport>() as u64;
|
||||
|
||||
impl PvmemcontrolTransport {
|
||||
fn ack() -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion { unit: () },
|
||||
command: PvmemcontrolTransportCommand::Ack,
|
||||
}
|
||||
}
|
||||
|
||||
fn error() -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion { unit: () },
|
||||
command: PvmemcontrolTransportCommand::Error,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_response(command: u32) -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion {
|
||||
register_response: PvmemcontrolTransportRegisterResponse {
|
||||
command: command.into(),
|
||||
_padding: 0,
|
||||
},
|
||||
},
|
||||
command: PvmemcontrolTransportCommand::Ack,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
||||
self.payload.register
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||
unsafe impl ByteValued for PvmemcontrolTransportUnion {}
|
||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||
unsafe impl ByteValued for PvmemcontrolTransport {}
|
||||
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, TryFromPrimitive, Debug)]
|
||||
enum FunctionCode {
|
||||
Info = 0,
|
||||
Dontneed = 1,
|
||||
Remove = 2,
|
||||
Free = 3,
|
||||
Pageout = 4,
|
||||
Dontdump = 5,
|
||||
SetVMAAnonName = 6,
|
||||
Mlock = 7,
|
||||
Munlock = 8,
|
||||
MprotectNone = 9,
|
||||
MprotectR = 10,
|
||||
MprotectW = 11,
|
||||
MprotectRW = 12,
|
||||
Mergeable = 13,
|
||||
Unmergeable = 14,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct PvmemcontrolReq {
|
||||
func_code: Le64,
|
||||
addr: Le64,
|
||||
length: Le64,
|
||||
arg: Le64,
|
||||
}
|
||||
|
||||
// SAFETY: it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for PvmemcontrolReq {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct PvmemcontrolResp {
|
||||
ret_errno: Le32,
|
||||
ret_code: Le32,
|
||||
ret_value: Le64,
|
||||
arg0: Le64,
|
||||
arg1: Le64,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PvmemcontrolResp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let PvmemcontrolResp {
|
||||
ret_errno,
|
||||
ret_code,
|
||||
..
|
||||
} = self;
|
||||
write!(
|
||||
f,
|
||||
"PvmemcontrolResp {{ ret_errno: {}, ret_code: {}, .. }}",
|
||||
ret_errno.to_native(),
|
||||
ret_code.to_native()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for PvmemcontrolResp {}
|
||||
|
||||
/// The guest connections start at 0x8000_0000, which has a leading 1 in
|
||||
/// the most significant byte, this ensures it does not conflict with
|
||||
/// any of the transport commands
|
||||
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct GuestConnection {
|
||||
command: u32,
|
||||
}
|
||||
|
||||
impl Default for GuestConnection {
|
||||
fn default() -> Self {
|
||||
GuestConnection::new(0x8000_0000)
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestConnection {
|
||||
fn new(command: u32) -> Self {
|
||||
Self { command }
|
||||
}
|
||||
|
||||
fn next(&self) -> Self {
|
||||
let GuestConnection { command } = *self;
|
||||
|
||||
if command == u32::MAX {
|
||||
GuestConnection::default()
|
||||
} else {
|
||||
GuestConnection::new(command + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for GuestConnection {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
if (value & 0x8000_0000) != 0 {
|
||||
Ok(GuestConnection::new(value))
|
||||
} else {
|
||||
Err(Error::InvalidConnection(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PercpuInitState {
|
||||
port_buf_map: HashMap<GuestConnection, GuestAddress>,
|
||||
next_conn: GuestConnection,
|
||||
}
|
||||
|
||||
impl PercpuInitState {
|
||||
fn new() -> Self {
|
||||
PercpuInitState {
|
||||
port_buf_map: HashMap::new(),
|
||||
next_conn: GuestConnection::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PvmemcontrolState {
|
||||
PercpuInit(PercpuInitState),
|
||||
Ready(HashMap<GuestConnection, GuestAddress>),
|
||||
Broken,
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolDevice {
|
||||
transport: PvmemcontrolTransport,
|
||||
state: PvmemcontrolState,
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
fn new(transport: PvmemcontrolTransport, state: PvmemcontrolState) -> Self {
|
||||
PvmemcontrolDevice { transport, state }
|
||||
}
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
fn register_percpu_buf(
|
||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
mut state: PercpuInitState,
|
||||
PvmemcontrolTransportRegister { buf_phys_addr }: PvmemcontrolTransportRegister,
|
||||
) -> Self {
|
||||
// access to this address is checked
|
||||
let buf_phys_addr = GuestAddress(buf_phys_addr.into());
|
||||
if !guest_memory.memory().check_range(
|
||||
buf_phys_addr,
|
||||
std::mem::size_of::<PvmemcontrolResp>().max(std::mem::size_of::<PvmemcontrolReq>()),
|
||||
) {
|
||||
warn!("guest sent invalid phys addr {:#x}", buf_phys_addr.0);
|
||||
return PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::error(),
|
||||
PvmemcontrolState::Broken,
|
||||
);
|
||||
}
|
||||
|
||||
let conn = {
|
||||
// find an available port+byte combination, and fail if full
|
||||
let mut next_conn = state.next_conn;
|
||||
while state.port_buf_map.contains_key(&next_conn) {
|
||||
next_conn = next_conn.next();
|
||||
if next_conn == state.next_conn {
|
||||
warn!("connections exhausted");
|
||||
return PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::error(),
|
||||
PvmemcontrolState::Broken,
|
||||
);
|
||||
}
|
||||
}
|
||||
next_conn
|
||||
};
|
||||
state.next_conn = conn.next();
|
||||
state.port_buf_map.insert(conn, buf_phys_addr);
|
||||
|
||||
// inform guest of the connection
|
||||
let response = PvmemcontrolTransport::register_response(conn.command);
|
||||
|
||||
PvmemcontrolDevice::new(response, PvmemcontrolState::PercpuInit(state))
|
||||
}
|
||||
|
||||
fn reset() -> Self {
|
||||
PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::ack(),
|
||||
PvmemcontrolState::PercpuInit(PercpuInitState::new()),
|
||||
)
|
||||
}
|
||||
|
||||
fn error() -> Self {
|
||||
PvmemcontrolDevice::new(PvmemcontrolTransport::error(), PvmemcontrolState::Broken)
|
||||
}
|
||||
|
||||
fn ready(PercpuInitState { port_buf_map, .. }: PercpuInitState) -> Self {
|
||||
PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::ack(),
|
||||
PvmemcontrolState::Ready(port_buf_map),
|
||||
)
|
||||
}
|
||||
|
||||
fn run_command(
|
||||
&mut self,
|
||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
command: PvmemcontrolTransportCommand,
|
||||
) {
|
||||
let state = std::mem::replace(&mut self.state, PvmemcontrolState::Broken);
|
||||
|
||||
*self = match command {
|
||||
PvmemcontrolTransportCommand::Reset => Self::reset(),
|
||||
PvmemcontrolTransportCommand::Register => {
|
||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||
// SAFETY: By device protocol. If driver is wrong the device
|
||||
// can enter a Broken state, but the behavior is still sound.
|
||||
Self::register_percpu_buf(guest_memory, state, unsafe {
|
||||
self.transport.as_register()
|
||||
})
|
||||
} else {
|
||||
debug!("received register without reset");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
PvmemcontrolTransportCommand::Ready => {
|
||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||
Self::ready(state)
|
||||
} else {
|
||||
debug!("received ready without reset");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
PvmemcontrolTransportCommand::Disconnect => Self::error(),
|
||||
PvmemcontrolTransportCommand::Ack => {
|
||||
debug!("received ack as command");
|
||||
Self::error()
|
||||
}
|
||||
PvmemcontrolTransportCommand::Error => {
|
||||
debug!("received error as command");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// read from the transport
|
||||
fn read_transport(&self, offset: u64, data: &mut [u8]) {
|
||||
self.transport
|
||||
.as_slice()
|
||||
.iter()
|
||||
.skip(offset as usize)
|
||||
.zip(data.iter_mut())
|
||||
.for_each(|(src, dest)| *dest = *src)
|
||||
}
|
||||
|
||||
/// can only write to transport payload
|
||||
/// command is a special register that needs separate dispatching
|
||||
fn write_transport(&mut self, offset: u64, data: &[u8]) {
|
||||
self.transport
|
||||
.payload
|
||||
.as_mut_slice()
|
||||
.iter_mut()
|
||||
.skip(offset as usize)
|
||||
.zip(data.iter())
|
||||
.for_each(|(dest, src)| *dest = *src)
|
||||
}
|
||||
|
||||
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
||||
match &self.state {
|
||||
PvmemcontrolState::Ready(map) => map.get(&conn).copied(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolBusDevice {
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
dev: RwLock<PvmemcontrolDevice>,
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolPciDevice {
|
||||
id: String,
|
||||
configuration: PciConfiguration,
|
||||
bar_regions: Vec<PciBarConfiguration>,
|
||||
}
|
||||
|
||||
impl PvmemcontrolBusDevice {
|
||||
/// f is called with the host address of `range_base` and only when
|
||||
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
||||
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
||||
where
|
||||
F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int,
|
||||
{
|
||||
let memory = self.mem.memory();
|
||||
let range_base = GuestAddress(addr);
|
||||
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
||||
|
||||
// assume guest memory is not interleaved with vmm memory on the host.
|
||||
if !memory.check_range(range_base, range_len) {
|
||||
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
||||
range_base,
|
||||
)));
|
||||
}
|
||||
let hva = memory
|
||||
.get_host_address(range_base)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
let res = f(hva as *mut libc::c_void, range_len as libc::size_t);
|
||||
if res != 0 {
|
||||
return Err(Error::LibcFail(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn madvise(&self, addr: u64, length: u64, advice: libc::c_int) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::madvise(base, len, advice)
|
||||
})
|
||||
}
|
||||
|
||||
fn mlock(&self, addr: u64, length: u64, on_default: bool) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::mlock2(base, len, if on_default { libc::MLOCK_ONFAULT } else { 0 })
|
||||
})
|
||||
}
|
||||
|
||||
fn munlock(&self, addr: u64, length: u64) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::munlock(base, len)
|
||||
})
|
||||
}
|
||||
|
||||
fn mprotect(
|
||||
&self,
|
||||
addr: u64,
|
||||
length: u64,
|
||||
protection: libc::c_int,
|
||||
) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::mprotect(base, len, protection)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
||||
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{}", name)).unwrap());
|
||||
let name_ptr = if let Some(name) = &name {
|
||||
name.as_ptr()
|
||||
} else {
|
||||
std::ptr::null()
|
||||
};
|
||||
debug!("addr {:X} length {} name {:?}", addr, length, name);
|
||||
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::prctl(
|
||||
libc::PR_SET_VMA,
|
||||
libc::PR_SET_VMA_ANON_NAME,
|
||||
base,
|
||||
len,
|
||||
name_ptr,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn process_request(
|
||||
&self,
|
||||
func_code: FunctionCode,
|
||||
addr: u64,
|
||||
length: u64,
|
||||
arg: u64,
|
||||
) -> Result<PvmemcontrolResp, Error> {
|
||||
let result = match func_code {
|
||||
FunctionCode::Info => {
|
||||
return Ok(PvmemcontrolResp {
|
||||
ret_errno: 0.into(),
|
||||
ret_code: 0.into(),
|
||||
ret_value: get_page_size().into(),
|
||||
arg0: MAJOR_VERSION.into(),
|
||||
arg1: MINOR_VERSION.into(),
|
||||
})
|
||||
}
|
||||
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
||||
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
||||
FunctionCode::Free => self.madvise(addr, length, libc::MADV_FREE),
|
||||
FunctionCode::Pageout => self.madvise(addr, length, libc::MADV_PAGEOUT),
|
||||
FunctionCode::Dontdump => self.madvise(addr, length, libc::MADV_DONTDUMP),
|
||||
FunctionCode::SetVMAAnonName => self.set_vma_anon_name(addr, length, arg),
|
||||
FunctionCode::Mlock => self.mlock(addr, length, false),
|
||||
FunctionCode::Munlock => self.munlock(addr, length),
|
||||
FunctionCode::MprotectNone => self.mprotect(addr, length, libc::PROT_NONE),
|
||||
FunctionCode::MprotectR => self.mprotect(addr, length, libc::PROT_READ),
|
||||
FunctionCode::MprotectW => self.mprotect(addr, length, libc::PROT_WRITE),
|
||||
FunctionCode::MprotectRW => {
|
||||
self.mprotect(addr, length, libc::PROT_READ | libc::PROT_WRITE)
|
||||
}
|
||||
FunctionCode::Mergeable => self.madvise(addr, length, libc::MADV_MERGEABLE),
|
||||
FunctionCode::Unmergeable => self.madvise(addr, length, libc::MADV_UNMERGEABLE),
|
||||
};
|
||||
result.map(|_| PvmemcontrolResp::default())
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
&self,
|
||||
PvmemcontrolReq {
|
||||
func_code,
|
||||
addr,
|
||||
length,
|
||||
arg,
|
||||
}: PvmemcontrolReq,
|
||||
) -> Result<PvmemcontrolResp, Error> {
|
||||
let (func_code, addr, length, arg) = (
|
||||
func_code.to_native(),
|
||||
addr.to_native(),
|
||||
length.to_native(),
|
||||
arg.to_native(),
|
||||
);
|
||||
|
||||
let resp_or_err = FunctionCode::try_from(func_code)
|
||||
.map_err(|_| Error::UnknownFunctionCode(func_code))
|
||||
.and_then(|func_code| self.process_request(func_code, addr, length, arg));
|
||||
|
||||
let resp = match resp_or_err {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => match e {
|
||||
Error::InvalidArgument(arg) => PvmemcontrolResp {
|
||||
ret_errno: (libc::EINVAL as u32).into(),
|
||||
ret_code: (arg as u32).into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::LibcFail(err) => PvmemcontrolResp {
|
||||
ret_errno: (err.raw_os_error().unwrap_or(libc::EFAULT) as u32).into(),
|
||||
ret_code: 0u32.into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::UnknownFunctionCode(func_code) => PvmemcontrolResp {
|
||||
ret_errno: (libc::EOPNOTSUPP as u32).into(),
|
||||
ret_code: (func_code as u32).into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::GuestMemory(err) => {
|
||||
warn!("{}", err);
|
||||
PvmemcontrolResp {
|
||||
ret_errno: (libc::EINVAL as u32).into(),
|
||||
ret_code: (func_code as u32).into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
// device error, stop responding
|
||||
other => return Err(other),
|
||||
},
|
||||
};
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn handle_pvmemcontrol_request(&self, guest_addr: GuestAddress) {
|
||||
let request: PvmemcontrolReq = if let Ok(x) = self.mem.memory().read_obj(guest_addr) {
|
||||
x
|
||||
} else {
|
||||
warn!("cannot read from guest address {:#x}", guest_addr.0);
|
||||
return;
|
||||
};
|
||||
|
||||
let response: PvmemcontrolResp = match self.handle_request(request) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
warn!("cannot process request {:?} with error {}", request, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if self.mem.memory().write_obj(response, guest_addr).is_err() {
|
||||
warn!("cannot write to guest address {:#x}", guest_addr.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_guest_write(&self, offset: u64, data: &[u8]) {
|
||||
if offset as usize != std::mem::offset_of!(PvmemcontrolTransport, command) {
|
||||
if data.len() != 4 && data.len() != 8 {
|
||||
warn!("guest write is not 4 or 8 bytes long");
|
||||
return;
|
||||
}
|
||||
self.dev.write().unwrap().write_transport(offset, data);
|
||||
return;
|
||||
}
|
||||
let data = if data.len() == 4 {
|
||||
let mut d = [0u8; 4];
|
||||
d.iter_mut()
|
||||
.zip(data.iter())
|
||||
.for_each(|(d, data)| *d = *data);
|
||||
d
|
||||
} else {
|
||||
warn!("guest write with non u32 at command register");
|
||||
return;
|
||||
};
|
||||
let data_cmd = u32::from_le_bytes(data);
|
||||
let command = PvmemcontrolTransportCommand::try_from(data_cmd);
|
||||
|
||||
match command {
|
||||
Ok(command) => self.dev.write().unwrap().run_command(&self.mem, command),
|
||||
Err(_) => {
|
||||
GuestConnection::try_from(data_cmd)
|
||||
.and_then(|conn| {
|
||||
self.dev
|
||||
.read()
|
||||
.unwrap()
|
||||
.find_connection(conn)
|
||||
.ok_or(Error::InvalidConnection(conn.command))
|
||||
})
|
||||
.map(|gpa| self.handle_pvmemcontrol_request(gpa))
|
||||
.unwrap_or_else(|err| warn!("{:?}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
||||
self.dev.read().unwrap().read_transport(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
pub fn make_device(
|
||||
id: String,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
) -> (PvmemcontrolPciDevice, PvmemcontrolBusDevice) {
|
||||
let dev = RwLock::new(PvmemcontrolDevice::error());
|
||||
let mut configuration = PciConfiguration::new(
|
||||
PVMEMCONTROL_VENDOR_ID,
|
||||
PVMEMCONTROL_DEVICE_ID,
|
||||
0x1,
|
||||
PciClassCode::BaseSystemPeripheral,
|
||||
&PvmemcontrolSubclass::Other,
|
||||
None,
|
||||
PciHeaderType::Device,
|
||||
PVMEMCONTROL_SUBSYSTEM_VENDOR_ID,
|
||||
PVMEMCONTROL_SUBSYSTEM_ID,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let command: [u8; 2] = [0x03, 0x01]; // memory, io, SERR#
|
||||
|
||||
configuration.write_config_register(1, 0, &command);
|
||||
(
|
||||
PvmemcontrolPciDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: Vec::new(),
|
||||
},
|
||||
PvmemcontrolBusDevice { mem, dev },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for PvmemcontrolPciDevice {
|
||||
fn write_config_register(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Option<Arc<Barrier>> {
|
||||
self.configuration
|
||||
.write_config_register(reg_idx, offset, data);
|
||||
None
|
||||
}
|
||||
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
self.configuration.read_config_register(reg_idx)
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn detect_bar_reprogramming(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
data: &[u8],
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
||||
}
|
||||
|
||||
fn allocate_bars(
|
||||
&mut self,
|
||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
resources: Option<Vec<Resource>>,
|
||||
) -> Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||
let mut bars = Vec::new();
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
let bar_id = 0;
|
||||
let region_size = PVMEMCONTROL_DEVICE_MMIO_SIZE;
|
||||
let restoring = resources.is_some();
|
||||
let bar_addr = mmio32_allocator
|
||||
.allocate(None, region_size, Some(PVMEMCONTROL_DEVICE_MMIO_ALIGN))
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
|
||||
let bar = PciBarConfiguration::default()
|
||||
.set_index(bar_id as usize)
|
||||
.set_address(bar_addr.raw_value())
|
||||
.set_size(region_size)
|
||||
.set_region_type(region_type)
|
||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||
|
||||
if !restoring {
|
||||
self.configuration
|
||||
.add_pci_bar(&bar)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
bars.push(bar);
|
||||
self.bar_regions.clone_from(&bars);
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
) -> Result<(), PciDeviceError> {
|
||||
for bar in self.bar_regions.drain(..) {
|
||||
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), io::Error> {
|
||||
for bar in self.bar_regions.iter_mut() {
|
||||
if bar.addr() == old_base {
|
||||
*bar = bar.set_address(new_base);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for PvmemcontrolPciDevice {
|
||||
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for PvmemcontrolPciDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut snapshot = Snapshot::new_from_state(&())?;
|
||||
|
||||
// Snapshot PciConfiguration
|
||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||
|
||||
Ok(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for PvmemcontrolPciDevice {}
|
||||
impl Migratable for PvmemcontrolPciDevice {}
|
||||
|
||||
impl BusDeviceSync for PvmemcontrolBusDevice {
|
||||
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
self.handle_guest_read(offset, data)
|
||||
}
|
||||
|
||||
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
self.handle_guest_write(offset, data);
|
||||
None
|
||||
}
|
||||
}
|
||||
105
docs/landlock.md
Normal file
105
docs/landlock.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Sandboxing using Landlock
|
||||
|
||||
Landlock is a lightweight mechanism to allow unprivileged applications to
|
||||
sandbox themselves.
|
||||
|
||||
During initial stages of running, applications can define the set of resources
|
||||
(mostly files) they need to access during their lifetime. All such rules are
|
||||
used to create a ruleset. Once the ruleset is applied, the process cannot access
|
||||
any resources outside of the ruleset during its lifetime, even if it were
|
||||
compromised.
|
||||
|
||||
Under the scope of `read` and `write` access, Landlock currently allows some
|
||||
additional accesses (eg: for now, access to extended file attributes is always
|
||||
allowed). Eventually, Landlock will only allow accesses similar to Unix
|
||||
permissions.
|
||||
|
||||
## Host Setup
|
||||
|
||||
Landlock should be enabled in Host kernel to use it with cloud-hypervisor.
|
||||
Please following [Kernel-Support](https://docs.kernel.org/userspace-api/landlock.html#kernel-support) link to enable Landlock on Host kernel.
|
||||
|
||||
|
||||
Landlock support can be checked with following command:
|
||||
```
|
||||
$ sudo dmesg | grep -w landlock
|
||||
[ 0.000000] landlock: Up and running.
|
||||
```
|
||||
Linux kernel confirms Landlock support with above message in dmesg.
|
||||
|
||||
## Enable Landlock
|
||||
|
||||
At the time of enabling Landlock, Cloud-Hypervisor process needs the complete
|
||||
list of files it accesses over its lifetime. So, Landlock is enabled `vm_create`
|
||||
stage of guest boot.
|
||||
|
||||
### Command Line
|
||||
Append `--landlock` to Cloud-Hypervisor's command line to enable Landlock
|
||||
support.
|
||||
|
||||
If you expect guest to access additional paths after it boots
|
||||
(ex: during hotplug), those paths can be passed using `--landlock-rules` command
|
||||
line parameter.
|
||||
|
||||
### API
|
||||
Landlock can also be enabled during `vm.create` request by passing a config like below:
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"landlock_enable": true,
|
||||
"landlock_rules": [
|
||||
{
|
||||
"path": "/tmp/disk1",
|
||||
"access": "rw"
|
||||
},
|
||||
{
|
||||
"path": "/tmp/disk2",
|
||||
"access": "rw"
|
||||
}
|
||||
]
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Usage Examples
|
||||
|
||||
To enable Landlock:
|
||||
|
||||
```
|
||||
./cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--landlock
|
||||
```
|
||||
Hotplugging any new file-backed resources to above guest will result in
|
||||
**Permission Denied** error.
|
||||
|
||||
To enable Landlock with hotplug support:
|
||||
|
||||
```
|
||||
./cloud-hypervisor \
|
||||
--api-socket /tmpXXXX/ch.socket \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--landlock \
|
||||
--landlock-rules path="/path/to/hotplug1",access="rw" path="/path/to/hotplug2",access="rw"
|
||||
|
||||
./ch-remote --api-socket /tmpXXXX/ch.socket \
|
||||
add-disk "path=/path/to/hotplug/blk.raw"
|
||||
```
|
||||
|
||||
`--landlock-rules` accepts file or directory paths among its options.
|
||||
|
||||
# References
|
||||
|
||||
* https://landlock.io/
|
||||
@@ -8,6 +8,8 @@ e.g. `dev_cli.sh`. The only prerequisite is [Docker installation](https://docs.d
|
||||
Please note that upon its first invocation, this script will pull a
|
||||
fairly large container image.
|
||||
|
||||
## Run the Performance Tests
|
||||
|
||||
To generate metrics data for all available performance tests (including
|
||||
boot time, block I/O throughput, and network throughput & latency) and
|
||||
output the result into a json file:
|
||||
@@ -27,3 +29,92 @@ To generate metrics data for selected performance tests, e.g. boot time only:
|
||||
```
|
||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --report-file /tmp/metrics.json --test-filter boot_time
|
||||
```
|
||||
|
||||
To set custom timeout or test iterations for all performance tests:
|
||||
|
||||
```
|
||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --timeout 5 --iterations 10
|
||||
```
|
||||
|
||||
## Performance Tests Details
|
||||
|
||||
The following table lists the supported performance tests with default
|
||||
timeout and number of iterations. The `timeout` defines the maximum
|
||||
execution time of each test for each iteration. The `iteration` defines
|
||||
how many times a test needs to be executed to generate the final metrics
|
||||
data.
|
||||
|
||||
| **Type** | **Metric** | **Timeout(s)** | **Iterations** |
|
||||
|------------|--------------------------------------------|----------------|----------------|
|
||||
| Boot Time | boot_time_ms | 2 | 10 |
|
||||
| | boot_time_pmem_ms | 2 | 10 |
|
||||
| | boot_time_16_vcpus_ms | 2 | 10 |
|
||||
| | boot_time_16_vcpus_pmem_ms | 2 | 10 |
|
||||
| Virtio Net | virtio_net_latency_us | 10 | 5 |
|
||||
| | virtio_net_throughput_single_queue_rx_gbps | 10 | 5 |
|
||||
| | virtio_net_throughput_single_queue_tx_gbps | 10 | 5 |
|
||||
| | virtio_net_throughput_multi_queue_rx_gbps | 10 | 5 |
|
||||
| | virtio_net_throughput_multi_queue_tx_gbps | 10 | 5 |
|
||||
| Block | block_read_MiBps | 10 | 5 |
|
||||
| | block_write_MiBps | 10 | 5 |
|
||||
| | block_random_read_MiBps | 10 | 5 |
|
||||
| | block_random_write_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_read_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_write_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_random_read_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_random_write_MiBps | 10 | 5 |
|
||||
| | block_read_IOPS | 10 | 5 |
|
||||
| | block_write_IOPS | 10 | 5 |
|
||||
| | block_random_read_IOPS | 10 | 5 |
|
||||
| | block_random_write_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_read_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_write_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_random_read_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_random_write_IOPS | 10 | 5 |
|
||||
| Other | restore_latency_time_ms | 2 | 10 |
|
||||
|
||||
## Output Format
|
||||
|
||||
Performance-metrics output the result into a json file if `report-file`
|
||||
param is set. The fields included in JSON include:
|
||||
|
||||
| Field Name | Content |
|
||||
|--------------------|------------------------------------------|
|
||||
| git_human_readable | Recent tag information of git repository |
|
||||
| git_revision | Commit id of HEAD |
|
||||
| git_commit_date | Commit date of HEAD |
|
||||
| date | Date for executing the program |
|
||||
| results | A list of metrics |
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example of generating metrics data for the boot time using
|
||||
`pmem`:
|
||||
|
||||
```bash
|
||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --test-filter boot_time_pmem_ms
|
||||
```
|
||||
|
||||
Here is a sample output:
|
||||
|
||||
```json
|
||||
{
|
||||
"git_human_readable": "v40.0",
|
||||
"git_revision": "e9b263975786abbf895469b93dfc00f21ce39a88",
|
||||
"git_commit_date": "Fri Jun 21 08:40:44 2024 +0000",
|
||||
"date": "Tue Jul 16 16:35:29 UTC 2024",
|
||||
"results": [
|
||||
{
|
||||
"name": "boot_time_pmem_ms",
|
||||
"mean": 105.9461,
|
||||
"std_dev": 7.140993312558129,
|
||||
"max": 120.01499999999999,
|
||||
"min": 92.37600000000002
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note that the metrics data above is for illustration purpose only and
|
||||
does not represent the actual performance of Cloud Hypervisor on your
|
||||
system.
|
||||
|
||||
@@ -6,7 +6,7 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
flume = "0.11.0"
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
once_cell = "1.19.0"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
|
||||
303
fuzz/Cargo.lock
generated
303
fuzz/Cargo.lock
generated
@@ -12,9 +12,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
version = "0.6.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -27,33 +27,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
|
||||
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
@@ -61,9 +61,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -111,9 +111,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
@@ -149,13 +149,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.99"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
|
||||
checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -166,30 +166,30 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.7"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.7"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim 0.11.1",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
@@ -217,9 +217,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "crc-any"
|
||||
@@ -232,9 +232,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.8"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -242,23 +242,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.8"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.8"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
@@ -278,12 +278,13 @@ dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arch",
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"num_enum",
|
||||
"pci",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -295,16 +296,42 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epoll"
|
||||
version = "4.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
@@ -358,7 +385,7 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6341b3480afbb34eaefc7f92713bc92f2d83e338aaa1c44192f9c2956f4a4903"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"managed",
|
||||
@@ -389,6 +416,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
@@ -414,10 +447,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
name = "indexmap"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
@@ -427,9 +470,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -460,12 +503,23 @@ version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bedae2ca4a531bebe311abaf9691f5cc14eaa21475243caa2e39c43bb872947d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "landlock"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dafb8a4afee64f167eb2b52d32f0eea002e41a7a6450e68c799c8ec3a81a634c"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
@@ -504,9 +558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
@@ -514,6 +568,12 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
version = "0.1.0"
|
||||
@@ -567,6 +627,27 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@@ -605,10 +686,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.83"
|
||||
name = "proc-macro-crate"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -687,9 +777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.117"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -698,9 +788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.8.1"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
|
||||
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -709,9 +799,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.8.1"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
|
||||
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -723,6 +813,12 @@ dependencies = [
|
||||
name = "serial_buffer"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
@@ -757,12 +853,6 @@ dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -771,9 +861,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.66"
|
||||
version = "2.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -782,24 +872,41 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tpm"
|
||||
version = "0.1.0"
|
||||
@@ -832,15 +939,15 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
@@ -892,7 +999,7 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6be08d1166d41a78861ad50212ab3f9eca0729c349ac3a7a8f557c62406b87cc"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1016,7 +1123,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"arch",
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -1027,6 +1134,7 @@ dependencies = [
|
||||
"gdbstub",
|
||||
"gdbstub_arch",
|
||||
"hypervisor",
|
||||
"landlock",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log",
|
||||
@@ -1162,9 +1270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@@ -1178,57 +1286,66 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.34"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
@@ -1236,9 +1353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.34"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -10,6 +10,7 @@ cargo-fuzz = true
|
||||
|
||||
[features]
|
||||
igvm = []
|
||||
pvmemcontrol = []
|
||||
|
||||
[dependencies]
|
||||
block = { path = "../block" }
|
||||
|
||||
@@ -180,6 +180,8 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
pvpanic: false,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
sgx_epc: None,
|
||||
@@ -190,6 +192,8 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
platform: None,
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
})),
|
||||
state: VmState::Running,
|
||||
memory_actual_size: 0,
|
||||
|
||||
@@ -12,24 +12,24 @@ sev_snp = ["igvm", "igvm_defs"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
byteorder = "1.5.0"
|
||||
igvm = { version = "0.3.1", optional = true }
|
||||
igvm = { version = "0.3.3", optional = true }
|
||||
igvm_defs = { version = "0.3.1", optional = true }
|
||||
kvm-bindings = { version = "0.8.1", optional = true, features = ["serde"] }
|
||||
kvm-ioctls = { version = "0.17.0", optional = true }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", features = [
|
||||
"fam-wrappers",
|
||||
"with-serde",
|
||||
], optional = true }
|
||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", optional = true }
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_with = { version = "3.7.0", default-features = false, features = [
|
||||
serde_with = { version = "3.9.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
|
||||
@@ -137,14 +137,6 @@ pub trait PlatformEmulator {
|
||||
///
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
|
||||
|
||||
/// Translate a guest virtual address into a physical one
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to translate.
|
||||
///
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError>;
|
||||
|
||||
/// Fetch instruction bytes from memory.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod cmp;
|
||||
pub mod mov;
|
||||
pub mod movs;
|
||||
pub mod or;
|
||||
pub mod stos;
|
||||
|
||||
fn get_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
|
||||
@@ -40,7 +40,7 @@ macro_rules! movs {
|
||||
let len = std::mem::size_of::<$bound>();
|
||||
|
||||
while count > 0 {
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
let mut memory: [u8; 8] = [0; 8];
|
||||
|
||||
let src = state
|
||||
.linearize(Register::DS, rsi, false)
|
||||
@@ -83,6 +83,11 @@ macro_rules! movs {
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Movsq_m64_m64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movsq_m64_m64 {
|
||||
movs!(u64);
|
||||
}
|
||||
|
||||
pub struct Movsd_m32_m32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movsd_m32_m32 {
|
||||
movs!(u32);
|
||||
@@ -103,6 +108,40 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
fn test_rep_movsq_m64_m64() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 32] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||
0xcd, 0xcd, 0xcd, 0xcd, // 0xcdcdcdcd
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0x48, 0xa5]; // rep movsq
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::ESI, 0),
|
||||
(Register::EDI, 0x10),
|
||||
];
|
||||
let mut data = [0u8; 8];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x10, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd12345678, <u64>::from_le_bytes(data));
|
||||
vmm.read_memory(0x18, &mut data).unwrap();
|
||||
assert_eq!(0xcdcdcdcd5aa55aa5, <u64>::from_le_bytes(data));
|
||||
// The rest should be default value 0 from MockVmm
|
||||
vmm.read_memory(0x20, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u64>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_movsd_m32_m32() {
|
||||
let ip: u64 = 0x1000;
|
||||
|
||||
236
hypervisor/src/arch/x86/emulator/instructions/stos.rs
Normal file
236
hypervisor/src/arch/x86/emulator/instructions/stos.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Copyright © 2024 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// STOS - Store String
|
||||
//
|
||||
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::DF;
|
||||
|
||||
macro_rules! stos {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let mut count: u64 = if insn.has_rep_prefix() {
|
||||
state
|
||||
.read_reg(Register::ECX)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let rax = state
|
||||
.read_reg(Register::RAX)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
let mut rdi = state
|
||||
.read_reg(Register::RDI)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
let df = (state.flags() & DF) != 0;
|
||||
let len = std::mem::size_of::<$bound>();
|
||||
let rax_bytes = rax.to_le_bytes();
|
||||
|
||||
while count > 0 {
|
||||
let dst = state
|
||||
.linearize(Register::ES, rdi, true)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
platform
|
||||
.write_memory(dst, &rax_bytes[0..len])
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
if df {
|
||||
rdi = rdi.wrapping_sub(len as u64);
|
||||
} else {
|
||||
rdi = rdi.wrapping_add(len as u64);
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
if insn.has_rep_prefix() {
|
||||
state
|
||||
.write_reg(Register::ECX, 0)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Stosq_m64_RAX;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosq_m64_RAX {
|
||||
stos!(u64);
|
||||
}
|
||||
|
||||
pub struct Stosd_m32_EAX;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosd_m32_EAX {
|
||||
stos!(u32);
|
||||
}
|
||||
|
||||
pub struct Stosw_m16_AX;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosw_m16_AX {
|
||||
stos!(u16);
|
||||
}
|
||||
|
||||
pub struct Stosb_m8_AL;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosb_m8_AL {
|
||||
stos!(u8);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosb() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 12] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||
];
|
||||
let insn = [0xf3, 0xaa]; // rep stosb
|
||||
let regs = vec![
|
||||
(Register::ECX, 3),
|
||||
(Register::EDI, 0x0),
|
||||
(Register::RAX, 0x123456ff),
|
||||
];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0, &mut data).unwrap();
|
||||
assert_eq!(0x12ffffff, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(4, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(8, &mut data).unwrap();
|
||||
assert_eq!(0x5aa55aa5, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stosw() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 4] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
];
|
||||
let insn = [0x66, 0xab]; // stosw
|
||||
let regs = vec![(Register::EDI, 0x1), (Register::AX, 0xaabb)];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0x12aabb78, <u32>::from_le_bytes(data));
|
||||
// The rest should be default value 0 from MockVmm
|
||||
vmm.read_memory(0x4, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8 + 8, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosw() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 8] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0x66, 0xf3, 0xab]; // rep stosw
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::EDI, 0x2),
|
||||
(Register::AX, 0xaabb),
|
||||
];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0xaabb5678, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x4, &mut data).unwrap();
|
||||
assert_eq!(0x0000aabb, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosd() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 12] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0xab]; // rep stosd
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::EDI, 0x8),
|
||||
(Register::EAX, 0xaabbccdd),
|
||||
];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
// Go backwards this time
|
||||
let mut state = vmm.cpu_state(0).unwrap();
|
||||
state.set_flags(state.flags() | DF);
|
||||
vmm.set_cpu_state(0, state).unwrap();
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0x12345678, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x4, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0xc, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosq() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 8] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0x48, 0xab]; // rep stosq
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::RDI, 0x0),
|
||||
(Register::RAX, 0x11223344aabbccdd),
|
||||
];
|
||||
let mut data = [0u8; 8];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0x11223344aabbccdd, <u64>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8, &mut data).unwrap();
|
||||
assert_eq!(0x11223344aabbccdd, <u64>::from_le_bytes(data));
|
||||
vmm.read_memory(0x10, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u64>::from_le_bytes(data));
|
||||
}
|
||||
}
|
||||
@@ -532,11 +532,17 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
(mov, Mov_moffs64_RAX),
|
||||
(mov, Mov_RAX_moffs64),
|
||||
// MOVS
|
||||
(movs, Movsq_m64_m64),
|
||||
(movs, Movsd_m32_m32),
|
||||
(movs, Movsw_m16_m16),
|
||||
(movs, Movsb_m8_m8),
|
||||
// OR
|
||||
(or, Or_rm8_r8)
|
||||
(or, Or_rm8_r8),
|
||||
// STOS
|
||||
(stos, Stosb_m8_AL),
|
||||
(stos, Stosw_m16_AX),
|
||||
(stos, Stosd_m32_EAX),
|
||||
(stos, Stosq_m64_RAX)
|
||||
);
|
||||
|
||||
handler
|
||||
@@ -561,7 +567,7 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
|
||||
decoder.set_ip(state.ip());
|
||||
|
||||
while decoder.can_decode() && !stop_emulation {
|
||||
while !stop_emulation {
|
||||
decoder.decode_out(&mut insn);
|
||||
|
||||
if decoder.last_error() == DecoderError::NoMoreBytes {
|
||||
@@ -588,6 +594,7 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
// Once we have the new stream, we must create a new decoder
|
||||
// and emulate one last instruction from the last decoded IP.
|
||||
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
|
||||
decoder.set_ip(last_decoded_ip);
|
||||
decoder.decode_out(&mut insn);
|
||||
if decoder.last_error() != DecoderError::None {
|
||||
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
||||
@@ -595,8 +602,6 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
insn_format!(insn)
|
||||
)));
|
||||
}
|
||||
|
||||
stop_emulation = true;
|
||||
}
|
||||
|
||||
// Emulate the decoded instruction
|
||||
@@ -751,10 +756,6 @@ mod mock_vmm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
Ok(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let rip = self
|
||||
.state
|
||||
@@ -771,12 +772,77 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// Emulate executing an empty stream. Instructions should be fetched from
|
||||
// memory.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_empty_instruction_stream() {
|
||||
let target_rax: u64 = 0x1000;
|
||||
let target_rbx: u64 = 0x1234567812345678;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &[], Some(2)).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rbx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate executing an empty stream. Instructions should be fetched from
|
||||
// memory. The emulation should abort.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// ... garbage ...
|
||||
fn test_empty_instruction_stream_bad() {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, // Garbage
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &[], None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// Test with a first instruction truncated.
|
||||
fn test_fetch_first_instruction() {
|
||||
let target_rax: u64 = 0x1000;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
@@ -801,7 +867,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, ip);
|
||||
assert_eq!(rax, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -839,6 +905,52 @@ mod tests {
|
||||
assert_eq!(rbx, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate only one instruction.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// The emulation should stop after the first instruction.
|
||||
fn test_emulate_one_instruction() {
|
||||
let target_rax: u64 = 0x1000;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(1)).is_ok());
|
||||
|
||||
let new_ip: u64 = vmm.cpu_state(cpu_id).unwrap().ip();
|
||||
assert_eq!(new_ip, ip + 0x7 /* length of mov rax,0x1000 */);
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
|
||||
// The second instruction is not executed so RBX should be zero.
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
|
||||
@@ -77,46 +77,61 @@ static VGIC_DIST_REGS: &[DistReg] = &[
|
||||
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
||||
];
|
||||
|
||||
fn dist_attr_access(gic: &DeviceFd, offset: u32, val: &u32, set: bool) -> Result<()> {
|
||||
fn dist_attr_set(gic: &DeviceFd, offset: u32, val: u32) -> Result<()> {
|
||||
let gic_dist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||
attr: offset as u64,
|
||||
addr: &val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dist_attr_get(gic: &DeviceFd, offset: u32) -> Result<u32> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gic_dist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||
attr: offset as u64,
|
||||
addr: val as *const u32 as u64,
|
||||
addr: &mut val as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_dist_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Get the distributor control register.
|
||||
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
|
||||
let val: u32 = 0;
|
||||
dist_attr_access(gic, GICD_CTLR, &val, false)?;
|
||||
Ok(val)
|
||||
dist_attr_get(gic, GICD_CTLR)
|
||||
}
|
||||
|
||||
/// Set the distributor control register.
|
||||
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
|
||||
dist_attr_access(gic, GICD_CTLR, &val, true)
|
||||
dist_attr_set(gic, GICD_CTLR, val)
|
||||
}
|
||||
|
||||
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
|
||||
let num_irq = 0;
|
||||
let mut num_irq = 0;
|
||||
|
||||
let mut nr_irqs_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
attr: 0,
|
||||
addr: &num_irq as *const u32 as u64,
|
||||
addr: &mut num_irq as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: nr_irqs_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut nr_irqs_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
@@ -158,8 +173,7 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
|
||||
let end = compute_reg_len(gic, dreg, base)?;
|
||||
|
||||
while base < end {
|
||||
let val = state[idx];
|
||||
dist_attr_access(gic, base, &val, true)?;
|
||||
dist_attr_set(gic, base, state[idx])?;
|
||||
idx += 1;
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
@@ -175,9 +189,7 @@ pub fn get_dist_regs(gic: &DeviceFd) -> Result<Vec<u32>> {
|
||||
let end = compute_reg_len(gic, dreg, base)?;
|
||||
|
||||
while base < end {
|
||||
let val: u32 = 0;
|
||||
dist_attr_access(gic, base, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(dist_attr_get(gic, base)?);
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,23 +79,38 @@ static VGIC_ICC_REGS: &[u64] = &[
|
||||
SYS_ICC_AP1R3_EL1,
|
||||
];
|
||||
|
||||
fn icc_attr_access(gic: &DeviceFd, offset: u64, typer: u64, val: &u32, set: bool) -> Result<()> {
|
||||
fn icc_attr_set(gic: &DeviceFd, offset: u64, typer: u64, val: u32) -> Result<()> {
|
||||
let gic_icc_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||
addr: &val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn icc_attr_get(gic: &DeviceFd, offset: u64, typer: u64) -> Result<u32> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gic_icc_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||
addr: val as *const u32 as u64,
|
||||
addr: &mut val as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_icc_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Get ICC registers.
|
||||
@@ -107,10 +122,9 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
for ix in gicr_typer {
|
||||
let i = *ix;
|
||||
for icc_offset in VGIC_ICC_REGS {
|
||||
let val = 0;
|
||||
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
||||
// calculate priority bits by reading the ctrl_el1 register.
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
let val = icc_attr_get(gic, *icc_offset, i)?;
|
||||
// The priority bits are found in the ICC_CTLR_EL1 register (bits from 10:8).
|
||||
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
|
||||
// architecture_specification.pdf.
|
||||
@@ -130,8 +144,7 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
// 7 bits of priority.
|
||||
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||
if num_priority_bits >= 6 {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||
}
|
||||
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|
||||
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
||||
@@ -139,12 +152,10 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||
{
|
||||
if num_priority_bits == 7 {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||
}
|
||||
} else {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +176,7 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
||||
}
|
||||
if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||
if num_priority_bits >= 6 {
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||
idx += 1;
|
||||
}
|
||||
continue;
|
||||
@@ -176,12 +187,12 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||
{
|
||||
if num_priority_bits == 7 {
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||
idx += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,34 +24,38 @@ const GITS_CWRITER: u32 = 0x0088;
|
||||
const GITS_CREADR: u32 = 0x0090;
|
||||
const GITS_BASER: u32 = 0x0100;
|
||||
|
||||
/// Access an ITS device attribute.
|
||||
///
|
||||
/// This is a helper function to get/set the ITS device attribute depending
|
||||
/// the bool parameter `set` provided.
|
||||
pub fn gicv3_its_attr_access(
|
||||
its_device: &DeviceFd,
|
||||
group: u32,
|
||||
attr: u32,
|
||||
val: &u64,
|
||||
set: bool,
|
||||
) -> Result<()> {
|
||||
fn gicv3_its_attr_set(its_device: &DeviceFd, group: u32, attr: u32, val: u64) -> Result<()> {
|
||||
let gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||
group,
|
||||
attr: attr as u64,
|
||||
addr: &val as *const u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
its_device
|
||||
.set_device_attr(&gicv3_its_attr)
|
||||
.map_err(|e| Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into())))
|
||||
}
|
||||
|
||||
fn gicv3_its_attr_get(its_device: &DeviceFd, group: u32, attr: u32) -> Result<u64> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||
group,
|
||||
attr: attr as u64,
|
||||
addr: val as *const u64 as u64,
|
||||
addr: &mut val as *mut u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
its_device.set_device_attr(&gicv3_its_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})
|
||||
} else {
|
||||
its_device
|
||||
.get_device_attr(&mut gicv3_its_attr)
|
||||
.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})
|
||||
}
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gicv3_its_attr.addr is safe to write to.
|
||||
its_device
|
||||
.get_device_attr(&mut gicv3_its_attr)
|
||||
.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Function that saves/restores ITS tables into guest RAM.
|
||||
@@ -324,60 +328,43 @@ impl Vgic for KvmGicV3Its {
|
||||
|
||||
let icc_state = get_icc_regs(&self.device, &gicr_typers)?;
|
||||
|
||||
let its_baser_state: [u64; 8] = [0; 8];
|
||||
let mut its_baser_state: [u64; 8] = [0; 8];
|
||||
for i in 0..8 {
|
||||
gicv3_its_attr_access(
|
||||
its_baser_state[i as usize] = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_BASER + i * 8,
|
||||
&its_baser_state[i as usize],
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
let its_ctlr_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_ctlr_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CTLR,
|
||||
&its_ctlr_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_cbaser_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_cbaser_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CBASER,
|
||||
&its_cbaser_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_creadr_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_creadr_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CREADR,
|
||||
&its_creadr_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_cwriter_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_cwriter_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CWRITER,
|
||||
&its_cwriter_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_iidr_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_iidr_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_IIDR,
|
||||
&its_iidr_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
Ok(Gicv3ItsState {
|
||||
@@ -407,57 +394,51 @@ impl Vgic for KvmGicV3Its {
|
||||
set_icc_regs(&self.device, &gicr_typers, &state.icc)?;
|
||||
|
||||
//Restore GICv3ITS registers
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_IIDR,
|
||||
&state.its_iidr,
|
||||
true,
|
||||
state.its_iidr,
|
||||
)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CBASER,
|
||||
&state.its_cbaser,
|
||||
true,
|
||||
state.its_cbaser,
|
||||
)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CREADR,
|
||||
&state.its_creadr,
|
||||
true,
|
||||
state.its_creadr,
|
||||
)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CWRITER,
|
||||
&state.its_cwriter,
|
||||
true,
|
||||
state.its_cwriter,
|
||||
)?;
|
||||
|
||||
for i in 0..8 {
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_BASER + i * 8,
|
||||
&state.its_baser[i as usize],
|
||||
true,
|
||||
state.its_baser[i as usize],
|
||||
)?;
|
||||
}
|
||||
|
||||
// Restore ITS tables
|
||||
gicv3_its_tables_access(self.its_device.as_ref().unwrap(), false)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CTLR,
|
||||
&state.its_ctlr,
|
||||
true,
|
||||
state.its_ctlr,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -96,23 +96,38 @@ static VGIC_SGI_REGS: &[RdistReg] = &[
|
||||
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
|
||||
];
|
||||
|
||||
fn redist_attr_access(gic: &DeviceFd, offset: u32, typer: u64, val: &u32, set: bool) -> Result<()> {
|
||||
fn redist_attr_set(gic: &DeviceFd, offset: u32, typer: u64, val: u32) -> Result<()> {
|
||||
let gic_redist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
|
||||
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
|
||||
addr: &val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn redist_attr_get(gic: &DeviceFd, offset: u32, typer: u64) -> Result<u32> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gic_redist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
|
||||
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
|
||||
addr: val as *const u32 as u64,
|
||||
addr: &mut val as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_redist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_redist_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_redist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn access_redists_aux(
|
||||
@@ -129,14 +144,11 @@ fn access_redists_aux(
|
||||
let end = base + rdreg.length as u32;
|
||||
|
||||
while base < end {
|
||||
let mut val = 0;
|
||||
if set {
|
||||
val = state[*idx];
|
||||
redist_attr_access(gic, base, *i, &val, true)?;
|
||||
redist_attr_set(gic, base, *i, state[*idx])?;
|
||||
*idx += 1;
|
||||
} else {
|
||||
redist_attr_access(gic, base, *i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(redist_attr_get(gic, base, *i)?);
|
||||
}
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{PlatformEmulator, PlatformError};
|
||||
|
||||
use crate::arch::emulator::PlatformEmulator;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState};
|
||||
use crate::arch::x86::emulator::Emulator;
|
||||
use crate::cpu;
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::hypervisor;
|
||||
use crate::mshv::emulator::MshvEmulatorContext;
|
||||
use crate::vec_with_array_field;
|
||||
use crate::vm::{self, InterruptSourceConfig, VmOps};
|
||||
use crate::HypervisorType;
|
||||
@@ -20,18 +19,15 @@ use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vfio_ioctls::VfioDeviceFd;
|
||||
use vm::DataMatch;
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
mod snp_constants;
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use snp_constants::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::ClockData;
|
||||
|
||||
use crate::{
|
||||
CpuState, IoEventAddress, IrqRoutingEntry, MpState, UserMemoryRegion,
|
||||
USER_MEMORY_REGION_ADJUSTABLE, USER_MEMORY_REGION_EXECUTE, USER_MEMORY_REGION_READ,
|
||||
@@ -39,18 +35,16 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use igvm_defs::IGVM_VHS_SNP_ID_BLOCK;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::VcpuMshvState;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use snp_constants::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
|
||||
pub use x86_64::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{emulator, VcpuMshvState};
|
||||
|
||||
const DIRTY_BITMAP_CLEAR_DIRTY: u64 = 0x4;
|
||||
const DIRTY_BITMAP_SET_DIRTY: u64 = 0x8;
|
||||
@@ -60,7 +54,7 @@ const DIRTY_BITMAP_SET_DIRTY: u64 = 0x8;
|
||||
///
|
||||
pub use {
|
||||
mshv_bindings::mshv_create_device as CreateDevice,
|
||||
mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls::DeviceFd,
|
||||
mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls, mshv_ioctls::DeviceFd,
|
||||
};
|
||||
|
||||
pub const PAGE_SHIFT: usize = 12;
|
||||
@@ -637,14 +631,18 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hv_message_type_HVMSG_UNMAPPED_GPA => {
|
||||
msg_type @ (hv_message_type_HVMSG_UNMAPPED_GPA
|
||||
| hv_message_type_HVMSG_GPA_INTERCEPT) => {
|
||||
let info = x.to_memory_info().unwrap();
|
||||
let insn_len = info.instruction_byte_count as usize;
|
||||
assert!(insn_len > 0 && insn_len <= 16);
|
||||
let gva = info.guest_virtual_address;
|
||||
let gpa = info.guest_physical_address;
|
||||
|
||||
debug!("Exit ({:?}) GVA {:x} GPA {:x}", msg_type, gva, gpa);
|
||||
|
||||
let mut context = MshvEmulatorContext {
|
||||
vcpu: self,
|
||||
map: (info.guest_virtual_address, info.guest_physical_address),
|
||||
map: (gva, gpa),
|
||||
};
|
||||
|
||||
// Create a new emulator.
|
||||
@@ -652,7 +650,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
|
||||
// Emulate the trapped instruction, and only the first one.
|
||||
let new_state = emul
|
||||
.emulate_first_insn(self.vp_index as usize, &info.instruction_bytes)
|
||||
.emulate_first_insn(
|
||||
self.vp_index as usize,
|
||||
&info.instruction_bytes[..insn_len],
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
// Set CPU state back.
|
||||
@@ -798,6 +799,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
GHCB_INFO_REGISTER_REQUEST => {
|
||||
let mut ghcb_gpa = hv_x64_register_sev_ghcb::default();
|
||||
|
||||
// Disable the previously used GHCB page.
|
||||
self.disable_prev_ghcb_page()?;
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
unsafe {
|
||||
ghcb_gpa.__bindgen_anon_1.set_enabled(1);
|
||||
@@ -825,6 +830,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
resp_ghcb_msr.__bindgen_anon_2.set_gpa_page_number(
|
||||
ghcb_msr.__bindgen_anon_2.gpa_page_number(),
|
||||
);
|
||||
debug!("GHCB GPA is {:x}", ghcb_gpa.as_uint64);
|
||||
}
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let reg_name_value = unsafe {
|
||||
@@ -925,15 +931,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
)?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
let mut swei1_rw_gpa_arg =
|
||||
mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd.gpa_write(&mut swei1_rw_gpa_arg).map_err(
|
||||
|e| cpu::HypervisorCpuError::GpaWrite(e.into()),
|
||||
)?;
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_QUERY => {
|
||||
let mut reg_assocs = [ hv_register_assoc {
|
||||
@@ -955,6 +953,9 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
self.fd.gpa_write(&mut swei2_rw_gpa_arg).map_err(
|
||||
|e| cpu::HypervisorCpuError::GpaWrite(e.into()),
|
||||
)?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_CLEAR => {
|
||||
let mut swei2_rw_gpa_arg =
|
||||
@@ -1055,14 +1056,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
let mut swei1_rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.gpa_write(&mut swei1_rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_EXITCODE_MMIO_READ => {
|
||||
let src_gpa =
|
||||
@@ -1091,6 +1085,9 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
self.fd
|
||||
.gpa_write(&mut arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_EXITCODE_MMIO_WRITE => {
|
||||
let dst_gpa =
|
||||
@@ -1119,6 +1116,9 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
cpu::HypervisorCpuError::RunVcpu(e.into())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_EXITCODE_SNP_GUEST_REQUEST => {
|
||||
let req_gpa =
|
||||
@@ -1164,15 +1164,8 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
.sev_snp_ap_create(&mshv_ap_create_req)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
let mut swei1_rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.fd
|
||||
.gpa_write(&mut swei1_rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
_ => panic!(
|
||||
"GHCB_INFO_NORMAL: Unhandled exit code: {:0x}",
|
||||
@@ -1468,6 +1461,42 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
|
||||
impl MshvVcpu {
|
||||
///
|
||||
/// Deactivate previously used GHCB page.
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn disable_prev_ghcb_page(&self) -> cpu::Result<()> {
|
||||
let mut reg_assocs = [hv_register_assoc {
|
||||
name: hv_register_name_HV_X64_REGISTER_SEV_GHCB_GPA,
|
||||
..Default::default()
|
||||
}];
|
||||
self.fd.get_reg(&mut reg_assocs).unwrap();
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let prev_ghcb_gpa = unsafe { reg_assocs[0].value.reg64 };
|
||||
|
||||
debug!("Prev GHCB GPA is {:x}", prev_ghcb_gpa);
|
||||
|
||||
let mut ghcb_gpa = hv_x64_register_sev_ghcb::default();
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
unsafe {
|
||||
ghcb_gpa.__bindgen_anon_1.set_enabled(0);
|
||||
ghcb_gpa.__bindgen_anon_1.set_page_number(prev_ghcb_gpa);
|
||||
}
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let reg_name_value = unsafe {
|
||||
[(
|
||||
hv_register_name_HV_X64_REGISTER_SEV_GHCB_GPA,
|
||||
ghcb_gpa.as_uint64,
|
||||
)]
|
||||
};
|
||||
|
||||
set_registers_64!(self.fd, reg_name_value)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xcrs".
|
||||
@@ -1509,133 +1538,26 @@ impl MshvVcpu {
|
||||
.set_vcpu_events(events)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
struct MshvEmulatorContext<'a> {
|
||||
vcpu: &'a MshvVcpu,
|
||||
map: (u64, u64), // Initial GVA to GPA mapping provided by the hypervisor
|
||||
}
|
||||
///
|
||||
/// Clear SW_EXIT_INFO1 register for SEV-SNP guests.
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn clear_swexit_info1(
|
||||
&self,
|
||||
ghcb_gpa: u64,
|
||||
) -> std::result::Result<cpu::VmExit, cpu::HypervisorCpuError> {
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
let mut swei1_rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.gpa_write(&mut swei1_rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl<'a> MshvEmulatorContext<'a> {
|
||||
// Do the actual gva -> gpa translation
|
||||
#[allow(non_upper_case_globals)]
|
||||
fn translate(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
if self.map.0 == gva {
|
||||
return Ok(self.map.1);
|
||||
}
|
||||
|
||||
// TODO: More fine-grained control for the flags
|
||||
let flags = HV_TRANSLATE_GVA_VALIDATE_READ | HV_TRANSLATE_GVA_VALIDATE_WRITE;
|
||||
|
||||
let (gpa, result_code) = self
|
||||
.vcpu
|
||||
.translate_gva(gva, flags.into())
|
||||
.map_err(|e| PlatformError::TranslateVirtualAddress(anyhow!(e)))?;
|
||||
|
||||
match result_code {
|
||||
hv_translate_gva_result_code_HV_TRANSLATE_GVA_SUCCESS => Ok(gpa),
|
||||
_ => Err(PlatformError::TranslateVirtualAddress(anyhow!(result_code))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// Platform emulation for Hyper-V
|
||||
impl<'a> PlatformEmulator for MshvEmulatorContext<'a> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva)?;
|
||||
debug!(
|
||||
"mshv emulator: memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_read(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_read(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryReadFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva)?;
|
||||
debug!(
|
||||
"mshv emulator: memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_write(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_write(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::GetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
let regs = self
|
||||
.vcpu
|
||||
.get_regs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
let sregs = self
|
||||
.vcpu
|
||||
.get_sregs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
|
||||
debug!("mshv emulator: Getting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", regs);
|
||||
|
||||
Ok(EmulatorCpuState { regs, sregs })
|
||||
}
|
||||
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::SetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
debug!("mshv emulator: Setting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", state.regs);
|
||||
|
||||
self.vcpu
|
||||
.set_regs(&state.regs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
self.vcpu
|
||||
.set_sregs(&state.sregs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
self.translate(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, _ip: u64, _instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
Err(PlatformError::MemoryReadFailure(anyhow!("unimplemented")))
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2130,4 +2052,38 @@ impl vm::Vm for MshvVm {
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gain_page_access(&self, gpa: u64, size: u32) -> vm::Result<()> {
|
||||
if !self.sev_snp_enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let start_gpfn: u64 = gpa >> PAGE_SHIFT;
|
||||
let end_gpfn: u64 = (gpa + size as u64 - 1) >> PAGE_SHIFT;
|
||||
|
||||
let gpas: Vec<u64> = (start_gpfn..=end_gpfn).map(|x| x << PAGE_SHIFT).collect();
|
||||
|
||||
if !gpas.is_empty() {
|
||||
let mut gpa_list = vec_with_array_field::<mshv_modify_gpa_host_access, u64>(gpas.len());
|
||||
gpa_list[0].gpa_list_size = gpas.len() as u64;
|
||||
gpa_list[0].host_access = HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE;
|
||||
gpa_list[0].acquire = 1;
|
||||
gpa_list[0].flags = 0;
|
||||
|
||||
// SAFETY: gpa_list initialized with gpas.len() and now it is being turned into
|
||||
// gpas_slice with gpas.len() again. It is guaranteed to be large enough to hold
|
||||
// everything from gpas.
|
||||
unsafe {
|
||||
let gpas_slice: &mut [u64] = gpa_list[0].gpa_list.as_mut_slice(gpas.len());
|
||||
gpas_slice.copy_from_slice(gpas.as_slice());
|
||||
}
|
||||
|
||||
self.fd
|
||||
.modify_gpa_host_access(&gpa_list[0])
|
||||
.map_err(|e| vm::HypervisorVmError::ModifyGpaHostAccess(e.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
145
hypervisor/src/mshv/x86_64/emulator.rs
Normal file
145
hypervisor/src/mshv/x86_64/emulator.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2024, Microsoft Corporation
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::{CpuStateManager, EmulatorCpuState};
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::mshv::MshvVcpu;
|
||||
use iced_x86::Register;
|
||||
use mshv_bindings::*;
|
||||
|
||||
pub struct MshvEmulatorContext<'a> {
|
||||
pub vcpu: &'a MshvVcpu,
|
||||
pub map: (u64, u64), // Initial GVA to GPA mapping provided by the hypervisor
|
||||
}
|
||||
|
||||
impl<'a> MshvEmulatorContext<'a> {
|
||||
// Do the actual gva -> gpa translation
|
||||
#[allow(non_upper_case_globals)]
|
||||
fn translate(&self, gva: u64, flags: u32) -> Result<u64, PlatformError> {
|
||||
if self.map.0 == gva {
|
||||
return Ok(self.map.1);
|
||||
}
|
||||
|
||||
let (gpa, result_code) = self
|
||||
.vcpu
|
||||
.translate_gva(gva, flags.into())
|
||||
.map_err(|e| PlatformError::TranslateVirtualAddress(anyhow!(e)))?;
|
||||
|
||||
match result_code {
|
||||
hv_translate_gva_result_code_HV_TRANSLATE_GVA_SUCCESS => Ok(gpa),
|
||||
_ => Err(PlatformError::TranslateVirtualAddress(anyhow!(result_code))),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_memory_flags(
|
||||
&self,
|
||||
gva: u64,
|
||||
data: &mut [u8],
|
||||
flags: u32,
|
||||
) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva, flags)?;
|
||||
debug!(
|
||||
"mshv emulator: memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_read(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_read(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryReadFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform emulation for Hyper-V
|
||||
impl<'a> PlatformEmulator for MshvEmulatorContext<'a> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
self.read_memory_flags(gva, data, HV_TRANSLATE_GVA_VALIDATE_READ)
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva, HV_TRANSLATE_GVA_VALIDATE_WRITE)?;
|
||||
debug!(
|
||||
"mshv emulator: memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_write(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_write(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::GetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
let regs = self
|
||||
.vcpu
|
||||
.get_regs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
let sregs = self
|
||||
.vcpu
|
||||
.get_sregs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
|
||||
debug!("mshv emulator: Getting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", regs);
|
||||
|
||||
Ok(EmulatorCpuState { regs, sregs })
|
||||
}
|
||||
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::SetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
debug!("mshv emulator: Setting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", state.regs);
|
||||
|
||||
self.vcpu
|
||||
.set_regs(&state.regs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
self.vcpu
|
||||
.set_sregs(&state.sregs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))
|
||||
}
|
||||
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let rip =
|
||||
self.cpu_state(self.vcpu.vp_index as usize)?
|
||||
.linearize(Register::CS, ip, false)?;
|
||||
self.read_memory_flags(
|
||||
rip,
|
||||
instruction_bytes,
|
||||
HV_TRANSLATE_GVA_VALIDATE_READ | HV_TRANSLATE_GVA_VALIDATE_EXECUTE,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ use crate::arch::x86::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of mshv_bindings for Unix-based platforms
|
||||
///
|
||||
|
||||
@@ -235,6 +235,12 @@ pub enum HypervisorVmError {
|
||||
///
|
||||
#[error("Failed to set VM property: {0}")]
|
||||
SetVmProperty(#[source] anyhow::Error),
|
||||
///
|
||||
/// Modify GPA host access error
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
#[error("Failed to modify GPA host access: {0}")]
|
||||
ModifyGpaHostAccess(#[source] anyhow::Error),
|
||||
}
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
@@ -404,6 +410,11 @@ pub trait Vm: Send + Sync + Any {
|
||||
fn resume(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gain_page_access(&self, _gpa: u64, _size: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VmOps: Send + Sync {
|
||||
|
||||
@@ -7,12 +7,12 @@ version = "0.1.0"
|
||||
[dependencies]
|
||||
epoll = "4.3.3"
|
||||
getrandom = "0.2.14"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
net_gen = { path = "../net_gen" }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
virtio-bindings = "0.2.2"
|
||||
virtio-queue = "0.12.0"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
@@ -25,6 +25,6 @@ vmm-sys-util = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
once_cell = "1.19.0"
|
||||
pnet = "0.34.0"
|
||||
pnet_datalink = "0.34.0"
|
||||
serde_json = "1.0.115"
|
||||
pnet = "0.35.0"
|
||||
pnet_datalink = "0.35.0"
|
||||
serde_json = "1.0.120"
|
||||
|
||||
@@ -19,6 +19,7 @@ use vm_virtio::{AccessPlatform, Translatable};
|
||||
pub struct TxVirtio {
|
||||
pub counter_bytes: Wrapping<u64>,
|
||||
pub counter_frames: Wrapping<u64>,
|
||||
iovecs: IovecBuffer,
|
||||
}
|
||||
|
||||
impl Default for TxVirtio {
|
||||
@@ -32,6 +33,7 @@ impl TxVirtio {
|
||||
TxVirtio {
|
||||
counter_bytes: Wrapping(0),
|
||||
counter_frames: Wrapping(0),
|
||||
iovecs: IovecBuffer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +56,7 @@ impl TxVirtio {
|
||||
|
||||
let mut next_desc = desc_chain.next();
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
let mut iovecs = self.iovecs.borrow();
|
||||
while let Some(desc) = next_desc {
|
||||
let desc_addr = desc
|
||||
.addr()
|
||||
@@ -145,6 +147,7 @@ impl TxVirtio {
|
||||
pub struct RxVirtio {
|
||||
pub counter_bytes: Wrapping<u64>,
|
||||
pub counter_frames: Wrapping<u64>,
|
||||
iovecs: IovecBuffer,
|
||||
}
|
||||
|
||||
impl Default for RxVirtio {
|
||||
@@ -158,6 +161,7 @@ impl RxVirtio {
|
||||
RxVirtio {
|
||||
counter_bytes: Wrapping(0),
|
||||
counter_frames: Wrapping(0),
|
||||
iovecs: IovecBuffer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +197,7 @@ impl RxVirtio {
|
||||
.ok_or(NetQueuePairError::DescriptorInvalidHeader)?;
|
||||
let mut next_desc = Some(desc);
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
let mut iovecs = self.iovecs.borrow();
|
||||
while let Some(desc) = next_desc {
|
||||
let desc_addr = desc
|
||||
.addr()
|
||||
@@ -288,6 +292,53 @@ impl RxVirtio {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct IovecBuffer(Vec<libc::iovec>);
|
||||
|
||||
// SAFETY: Implementing Send for IovecBuffer is safe as the pointer inside is iovec.
|
||||
// The iovecs are usually constructed from virtio descriptors, which are safe to send across
|
||||
// threads.
|
||||
unsafe impl Send for IovecBuffer {}
|
||||
// SAFETY: Implementing Sync for IovecBuffer is safe as the pointer inside is iovec.
|
||||
// The iovecs are usually constructed from virtio descriptors, which are safe to access from
|
||||
// multiple threads.
|
||||
unsafe impl Sync for IovecBuffer {}
|
||||
|
||||
impl IovecBuffer {
|
||||
fn new() -> Self {
|
||||
// Here we use 4 as the default capacity because it is enough for most cases.
|
||||
const DEFAULT_CAPACITY: usize = 4;
|
||||
IovecBuffer(Vec::with_capacity(DEFAULT_CAPACITY))
|
||||
}
|
||||
|
||||
fn borrow(&mut self) -> IovecBufferBorrowed<'_> {
|
||||
IovecBufferBorrowed(&mut self.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct IovecBufferBorrowed<'a>(&'a mut Vec<libc::iovec>);
|
||||
|
||||
impl<'a> std::ops::Deref for IovecBufferBorrowed<'a> {
|
||||
type Target = Vec<libc::iovec>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::DerefMut for IovecBufferBorrowed<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IovecBufferBorrowed<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Clear the buffer to make sure old values are not used after
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct NetCounters {
|
||||
pub tx_bytes: Arc<AtomicU64>,
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum OptionParserError {
|
||||
UnknownOption(String),
|
||||
InvalidSyntax(String),
|
||||
Conversion(String, String),
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for OptionParserError {
|
||||
@@ -33,6 +34,7 @@ impl fmt::Display for OptionParserError {
|
||||
OptionParserError::Conversion(field, value) => {
|
||||
write!(f, "unable to convert {value} for {field}")
|
||||
}
|
||||
OptionParserError::InvalidValue(s) => write!(f, "invalid value: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,7 +205,7 @@ impl FromStr for ByteSized {
|
||||
0
|
||||
};
|
||||
|
||||
let s = s.trim_end_matches(|c| c == 'K' || c == 'M' || c == 'G');
|
||||
let s = s.trim_end_matches(['K', 'M', 'G']);
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| ByteSizedParseError::InvalidValue(s.to_owned()))?
|
||||
<< shift
|
||||
|
||||
@@ -10,13 +10,13 @@ kvm = ["vfio-ioctls/kvm"]
|
||||
mshv = ["vfio-ioctls/mshv"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
byteorder = "1.5.0"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main", features = [
|
||||
"fam-wrappers",
|
||||
] }
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use vm_device::{Bus, BusDevice};
|
||||
use vm_device::{Bus, BusDevice, BusDeviceSync};
|
||||
|
||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57;
|
||||
@@ -122,7 +122,7 @@ impl PciBus {
|
||||
|
||||
pub fn register_mapping(
|
||||
&self,
|
||||
dev: Arc<Mutex<dyn BusDevice>>,
|
||||
dev: Arc<dyn BusDeviceSync>,
|
||||
#[cfg(target_arch = "x86_64")] io_bus: &Bus,
|
||||
mmio_bus: &Bus,
|
||||
bars: Vec<PciBarConfiguration>,
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::fmt::{self, Display};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::{io, result};
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDevice, Resource};
|
||||
use vm_device::Resource;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -54,7 +54,7 @@ pub struct BarReprogrammingParams {
|
||||
pub region_type: PciBarRegionType,
|
||||
}
|
||||
|
||||
pub trait PciDevice: BusDevice {
|
||||
pub trait PciDevice: Send {
|
||||
/// Allocates the needed PCI BARs space using the `allocate` function which takes a size and
|
||||
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.
|
||||
fn allocate_bars(
|
||||
|
||||
@@ -213,6 +213,12 @@ impl MsixConfig {
|
||||
let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize;
|
||||
let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO;
|
||||
|
||||
if index >= self.table_entries.len() {
|
||||
debug!("Invalid MSI-X table entry index {index}");
|
||||
data.copy_from_slice(&[0xff; 8][..data.len()]);
|
||||
return;
|
||||
}
|
||||
|
||||
match data.len() {
|
||||
4 => {
|
||||
let value = match modulo_offset {
|
||||
@@ -260,6 +266,11 @@ impl MsixConfig {
|
||||
let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize;
|
||||
let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO;
|
||||
|
||||
if index >= self.table_entries.len() {
|
||||
debug!("Invalid MSI-X table entry index {index}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the value of the entry before modification
|
||||
let old_entry = self.table_entries[index].clone();
|
||||
|
||||
@@ -351,6 +362,12 @@ impl MsixConfig {
|
||||
let index: usize = (offset / MSIX_PBA_ENTRIES_MODULO) as usize;
|
||||
let modulo_offset = offset % MSIX_PBA_ENTRIES_MODULO;
|
||||
|
||||
if index >= self.pba_entries.len() {
|
||||
debug!("Invalid MSI-X PBA entry index {index}");
|
||||
data.copy_from_slice(&[0xff; 8][..data.len()]);
|
||||
return;
|
||||
}
|
||||
|
||||
match data.len() {
|
||||
4 => {
|
||||
let value: u32 = match modulo_offset {
|
||||
|
||||
@@ -9,7 +9,7 @@ version = "0.1.0"
|
||||
clap = { version = "4.5.4", features = ["wrap_help"] }
|
||||
dirs = "5.0.1"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
test_infra = { path = "../test_infra" }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
@@ -274,7 +274,7 @@ mod adjuster {
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_LIST: [PerformanceTest; 29] = [
|
||||
const TEST_LIST: [PerformanceTest; 30] = [
|
||||
PerformanceTest {
|
||||
name: "boot_time_ms",
|
||||
func_ptr: performance_boot_time,
|
||||
@@ -306,6 +306,16 @@ const TEST_LIST: [PerformanceTest; 29] = [
|
||||
},
|
||||
unit_adjuster: adjuster::s_to_ms,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "restore_latency_time_ms",
|
||||
func_ptr: performance_restore_latency,
|
||||
control: PerformanceTestControl {
|
||||
test_timeout: 2,
|
||||
test_iterations: 10,
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::identity,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "boot_time_16_vcpus_pmem_ms",
|
||||
func_ptr: performance_boot_time_pmem,
|
||||
|
||||
@@ -23,6 +23,7 @@ pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-
|
||||
enum Error {
|
||||
BootTimeParse,
|
||||
Infra(InfraError),
|
||||
RestoreTimeParse,
|
||||
}
|
||||
|
||||
impl From<InfraError> for Error {
|
||||
@@ -76,6 +77,26 @@ fn direct_kernel_boot_path() -> PathBuf {
|
||||
kernel_path
|
||||
}
|
||||
|
||||
fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool {
|
||||
let mut cmd = std::process::Command::new(clh_command("ch-remote"));
|
||||
cmd.args([&format!("--api-socket={}", api_socket), command]);
|
||||
|
||||
if let Some(arg) = arg {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
let output = cmd.output().unwrap();
|
||||
if output.status.success() {
|
||||
true
|
||||
} else {
|
||||
eprintln!(
|
||||
"Error running ch-remote command: {:?}\nstderr: {}",
|
||||
&cmd,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
|
||||
let test_timeout = control.test_timeout;
|
||||
let (rx, bandwidth) = control.net_control.unwrap();
|
||||
@@ -255,7 +276,7 @@ fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, E
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
parse_boot_time_output(&output.stderr).map_err(|e| {
|
||||
parse_boot_time_output(&output.stderr).inspect_err(|_| {
|
||||
eprintln!(
|
||||
"\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
@@ -264,8 +285,6 @@ fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, E
|
||||
"\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
@@ -406,6 +425,120 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the event_monitor file based on the format that each event
|
||||
// is followed by a double newline
|
||||
fn parse_event_file(event_file: &str) -> Vec<serde_json::Value> {
|
||||
let content = fs::read(event_file).unwrap();
|
||||
let mut ret = Vec::new();
|
||||
for entry in String::from_utf8_lossy(&content)
|
||||
.trim()
|
||||
.split("\n\n")
|
||||
.collect::<Vec<&str>>()
|
||||
{
|
||||
ret.push(serde_json::from_str(entry).unwrap());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn parse_restore_time_output(events: &[serde_json::Value]) -> Result<f64, Error> {
|
||||
for entry in events.iter() {
|
||||
if entry["event"].as_str().unwrap() == "restored" {
|
||||
let duration = entry["timestamp"]["secs"].as_u64().unwrap() as f64 * 1_000f64
|
||||
+ entry["timestamp"]["nanos"].as_u64().unwrap() as f64 / 1_000_000f64;
|
||||
return Ok(duration);
|
||||
}
|
||||
}
|
||||
Err(Error::RestoreTimeParse)
|
||||
}
|
||||
|
||||
fn measure_restore_time(
|
||||
cmd: &mut GuestCommand,
|
||||
event_file: &str,
|
||||
test_timeout: u32,
|
||||
) -> Result<f64, Error> {
|
||||
let mut child = cmd
|
||||
.capture_output()
|
||||
.verbosity(VerbosityLevel::Warn)
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(Duration::new((test_timeout / 2) as u64, 0));
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
let json_events = parse_event_file(event_file);
|
||||
|
||||
parse_restore_time_output(&json_events).inspect_err(|_| {
|
||||
eprintln!(
|
||||
"\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====\
|
||||
\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 {
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
let api_socket_source = String::from(
|
||||
guest
|
||||
.tmp_dir
|
||||
.as_path()
|
||||
.join("cloud-hypervisor.sock")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(["--api-socket", &api_socket_source])
|
||||
.args([
|
||||
"--cpus",
|
||||
&format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
|
||||
])
|
||||
.args(["--memory", "size=256M"])
|
||||
.args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.args(["--console", "off"])
|
||||
.default_disks()
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(Duration::new((control.test_timeout / 2) as u64, 0));
|
||||
let snapshot_dir = String::from(guest.tmp_dir.as_path().join("snapshot").to_str().unwrap());
|
||||
std::fs::create_dir(&snapshot_dir).unwrap();
|
||||
assert!(remote_command(&api_socket_source, "pause", None));
|
||||
assert!(remote_command(
|
||||
&api_socket_source,
|
||||
"snapshot",
|
||||
Some(format!("file://{}", snapshot_dir).as_str()),
|
||||
));
|
||||
|
||||
let _ = child.kill();
|
||||
|
||||
let event_path = String::from(guest.tmp_dir.as_path().join("event.json").to_str().unwrap());
|
||||
let mut cmd = GuestCommand::new(&guest);
|
||||
let c = cmd
|
||||
.args([
|
||||
"--restore",
|
||||
format!("source_url=file://{}", snapshot_dir).as_str(),
|
||||
])
|
||||
.args(["--event-monitor", format!("path={}", event_path).as_str()]);
|
||||
|
||||
measure_restore_time(c, event_path.as_str(), control.test_timeout).unwrap()
|
||||
});
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -496,7 +629,42 @@ cloud-hypervisor: 613.57361ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port
|
||||
|
||||
assert_eq!(parse_boot_time_output(output.as_bytes()).unwrap(), 0.4525);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_restore_time_output() {
|
||||
let output = r#"
|
||||
{
|
||||
"timestamp": {
|
||||
"secs": 0,
|
||||
"nanos": 4664404
|
||||
},
|
||||
"source": "virtio-device",
|
||||
"event": "activated",
|
||||
"properties": {
|
||||
"id": "__rng"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"timestamp": {
|
||||
"secs": 0,
|
||||
"nanos": 5505133
|
||||
},
|
||||
"source": "vm",
|
||||
"event": "restored",
|
||||
"properties": null
|
||||
}
|
||||
"#;
|
||||
let mut ret = Vec::new();
|
||||
for entry in String::from(output)
|
||||
.trim()
|
||||
.split("\n\n")
|
||||
.collect::<Vec<&str>>()
|
||||
{
|
||||
ret.push(serde_json::from_str(entry).unwrap());
|
||||
}
|
||||
|
||||
assert_eq!(parse_restore_time_output(&ret).unwrap(), 5.505133_f64);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_fio_output() {
|
||||
let output = r#"
|
||||
|
||||
@@ -5,7 +5,7 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
thiserror = "1.0.60"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
thiserror = "1.0.62"
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
197
release-notes.md
197
release-notes.md
@@ -1,14 +1,20 @@
|
||||
- [v40.0](#v400)
|
||||
- [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices)
|
||||
- [v41.0](#v410)
|
||||
- [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support)
|
||||
- [Sandboxing With Landlock Support](#sandboxing-with-landlock-support)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [v40.0](#v400)
|
||||
- [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v39.0](#v390)
|
||||
- [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments)
|
||||
- [Direct Booting with bzImages](#direct-booting-with-bzimages)
|
||||
- [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support)
|
||||
- [Guest NMI Injection Support](#guest-nmi-injection-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [v38.0](#v380)
|
||||
- [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices)
|
||||
- [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread)
|
||||
@@ -16,16 +22,16 @@
|
||||
- [New 'debug-console' Device](#new-debug-console-device)
|
||||
- [Improved VFIO Device Support](#improved-vfio-device-support)
|
||||
- [Extended CPU Affinity Support](#extended-cpu-affinity-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [v37.0](#v370)
|
||||
- [Long Term Support (LTS) Release](#long-term-support-lts-release)
|
||||
- [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices)
|
||||
- [Configurable Named TAP Devices](#configurable-named-tap-devices)
|
||||
- [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console)
|
||||
- [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [v36.0](#v360)
|
||||
- [Command Line Changes](#command-line-changes)
|
||||
- [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli)
|
||||
@@ -34,31 +40,31 @@
|
||||
- [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port)
|
||||
- [AIO Backend for Block Devices](#aio-backend-for-block-devices)
|
||||
- [Documentation Improvements](#documentation-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [v35.0](#v350)
|
||||
- [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63)
|
||||
- [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block)
|
||||
- [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-6)
|
||||
- [Contributors](#contributors-6)
|
||||
- [v34.0](#v340)
|
||||
- [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support)
|
||||
- [Improvements to VM Core Dump](#improvements-to-vm-core-dump)
|
||||
- [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files)
|
||||
- [Minimum Host Kernel Bump](#minimum-host-kernel-bump)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-6)
|
||||
- [Contributors](#contributors-6)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-7)
|
||||
- [v33.0](#v330)
|
||||
- [D-Bus based API](#d-bus-based-api)
|
||||
- [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-7)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Contributors](#contributors-8)
|
||||
- [v32.0](#v320)
|
||||
- [Increased PCI Segment Limit](#increased-pci-segment-limit)
|
||||
- [API Changes](#api-changes)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Contributors](#contributors-8)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Contributors](#contributors-9)
|
||||
- [v31.1](#v311)
|
||||
- [v31.0](#v310)
|
||||
- [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables)
|
||||
@@ -66,15 +72,15 @@
|
||||
- [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler)
|
||||
- [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile)
|
||||
- [Documentation Improvements](#documentation-improvements-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Contributors](#contributors-9)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-10)
|
||||
- [Contributors](#contributors-10)
|
||||
- [v30.0](#v300)
|
||||
- [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size)
|
||||
- [Basic vfio-user Server Support](#basic-vfio-user-server-support)
|
||||
- [Heap Profiling Support](#heap-profiling-support)
|
||||
- [Documentation Improvements](#documentation-improvements-2)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-10)
|
||||
- [Contributors](#contributors-10)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-11)
|
||||
- [Contributors](#contributors-11)
|
||||
- [v28.2](#v282)
|
||||
- [v29.0](#v290)
|
||||
- [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm)
|
||||
@@ -84,10 +90,10 @@
|
||||
- [`AArch64` Documentation Integration](#aarch64-documentation-integration)
|
||||
- [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement)
|
||||
- [TCP Offload Control](#tcp-offload-control)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-11)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-12)
|
||||
- [Removals](#removals)
|
||||
- [Deprecations](#deprecations)
|
||||
- [Contributors](#contributors-11)
|
||||
- [Contributors](#contributors-12)
|
||||
- [v28.1](#v281)
|
||||
- [v28.0](#v280)
|
||||
- [Community Engagement (Reminder)](#community-engagement-reminder)
|
||||
@@ -95,9 +101,9 @@
|
||||
- [Virtualised TPM Support](#virtualised-tpm-support)
|
||||
- [Transparent Huge Page Support](#transparent-huge-page-support)
|
||||
- [README Quick Start Improved](#readme-quick-start-improved)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-12)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-13)
|
||||
- [Removals](#removals-1)
|
||||
- [Contributors](#contributors-12)
|
||||
- [Contributors](#contributors-13)
|
||||
- [v27.0](#v270)
|
||||
- [Community Engagement](#community-engagement)
|
||||
- [Prebuilt Packages](#prebuilt-packages)
|
||||
@@ -106,41 +112,41 @@
|
||||
- [Simplified Build Feature Flags](#simplified-build-feature-flags)
|
||||
- [Asynchronous Kernel Loading](#asynchronous-kernel-loading)
|
||||
- [GDB Support for AArch64](#gdb-support-for-aarch64)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-13)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-14)
|
||||
- [Deprecations](#deprecations-1)
|
||||
- [Contributors](#contributors-13)
|
||||
- [Contributors](#contributors-14)
|
||||
- [v26.0](#v260)
|
||||
- [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform)
|
||||
- [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-14)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-15)
|
||||
- [Deprecations](#deprecations-2)
|
||||
- [Removals](#removals-2)
|
||||
- [Contributors](#contributors-14)
|
||||
- [Contributors](#contributors-15)
|
||||
- [v25.0](#v250)
|
||||
- [`ch-remote` Improvements](#ch-remote-improvements-1)
|
||||
- [VM "Coredump" Support](#vm-coredump-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-15)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-16)
|
||||
- [Removals](#removals-3)
|
||||
- [Contributors](#contributors-15)
|
||||
- [Contributors](#contributors-16)
|
||||
- [v24.0](#v240)
|
||||
- [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu)
|
||||
- [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness)
|
||||
- [Sparse Mmap support](#sparse-mmap-support)
|
||||
- [Expose Platform Serial Number](#expose-platform-serial-number)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-16)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-17)
|
||||
- [Notable Improvements](#notable-improvements)
|
||||
- [Deprecations](#deprecations-3)
|
||||
- [New on the Website](#new-on-the-website)
|
||||
- [Contributors](#contributors-16)
|
||||
- [Contributors](#contributors-17)
|
||||
- [v23.1](#v231)
|
||||
- [v23.0](#v230)
|
||||
- [vDPA Support](#vdpa-support)
|
||||
- [Updated OS Support list](#updated-os-support-list)
|
||||
- [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements)
|
||||
- [`AMX` Support](#amx-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-17)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-18)
|
||||
- [Deprecations](#deprecations-4)
|
||||
- [Contributors](#contributors-17)
|
||||
- [Contributors](#contributors-18)
|
||||
- [v22.1](#v221)
|
||||
- [v22.0](#v220)
|
||||
- [GDB Debug Stub Support](#gdb-debug-stub-support)
|
||||
@@ -151,13 +157,13 @@
|
||||
- [PMU Support for AArch64](#pmu-support-for-aarch64)
|
||||
- [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license)
|
||||
- [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-18)
|
||||
- [Contributors](#contributors-18)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-19)
|
||||
- [Contributors](#contributors-19)
|
||||
- [v21.0](#v210)
|
||||
- [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade)
|
||||
- [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515)
|
||||
- [Notable Bug fixes](#notable-bug-fixes-19)
|
||||
- [Contributors](#contributors-19)
|
||||
- [Notable Bug fixes](#notable-bug-fixes-20)
|
||||
- [Contributors](#contributors-20)
|
||||
- [v20.2](#v202)
|
||||
- [v20.1](#v201)
|
||||
- [v20.0](#v200)
|
||||
@@ -166,8 +172,8 @@
|
||||
- [Improved VFIO support](#improved-vfio-support)
|
||||
- [Safer code](#safer-code)
|
||||
- [Extended documentation](#extended-documentation)
|
||||
- [Notable bug fixes](#notable-bug-fixes-20)
|
||||
- [Contributors](#contributors-20)
|
||||
- [Notable bug fixes](#notable-bug-fixes-21)
|
||||
- [Contributors](#contributors-21)
|
||||
- [v19.0](#v190)
|
||||
- [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console)
|
||||
- [PCI boot time optimisations](#pci-boot-time-optimisations)
|
||||
@@ -175,8 +181,8 @@
|
||||
- [Live migration enhancements](#live-migration-enhancements)
|
||||
- [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user)
|
||||
- [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu)
|
||||
- [Notable bug fixes](#notable-bug-fixes-21)
|
||||
- [Contributors](#contributors-21)
|
||||
- [Notable bug fixes](#notable-bug-fixes-22)
|
||||
- [Contributors](#contributors-22)
|
||||
- [v18.0](#v180)
|
||||
- [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support)
|
||||
- [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices)
|
||||
@@ -186,23 +192,23 @@
|
||||
- [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor)
|
||||
- [AArch64 CPU topology support](#aarch64-cpu-topology-support)
|
||||
- [Power button support on AArch64](#power-button-support-on-aarch64)
|
||||
- [Notable bug fixes](#notable-bug-fixes-22)
|
||||
- [Contributors](#contributors-22)
|
||||
- [Notable bug fixes](#notable-bug-fixes-23)
|
||||
- [Contributors](#contributors-23)
|
||||
- [v17.0](#v170)
|
||||
- [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi)
|
||||
- [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend)
|
||||
- [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices)
|
||||
- [Improved SGX support](#improved-sgx-support)
|
||||
- [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices)
|
||||
- [Notable bug fixes](#notable-bug-fixes-23)
|
||||
- [Contributors](#contributors-23)
|
||||
- [Notable bug fixes](#notable-bug-fixes-24)
|
||||
- [Contributors](#contributors-24)
|
||||
- [v16.0](#v160)
|
||||
- [Improved live migration support](#improved-live-migration-support)
|
||||
- [Improved `vhost-user` support](#improved-vhost-user-support)
|
||||
- [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support)
|
||||
- [Notable bug fixes](#notable-bug-fixes-24)
|
||||
- [Notable bug fixes](#notable-bug-fixes-25)
|
||||
- [Removed functionality](#removed-functionality)
|
||||
- [Contributors](#contributors-24)
|
||||
- [Contributors](#contributors-25)
|
||||
- [v15.0](#v150)
|
||||
- [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees)
|
||||
- [Network device rate limiting](#network-device-rate-limiting)
|
||||
@@ -210,7 +216,7 @@
|
||||
- [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter)
|
||||
- [Bug fixes](#bug-fixes)
|
||||
- [Deprecations](#deprecations-5)
|
||||
- [Contributors](#contributors-25)
|
||||
- [Contributors](#contributors-26)
|
||||
- [v0.14.1](#v0141)
|
||||
- [v0.14.0](#v0140)
|
||||
- [Structured event monitoring](#structured-event-monitoring)
|
||||
@@ -220,7 +226,7 @@
|
||||
- [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console)
|
||||
- [Block device rate limiting](#block-device-rate-limiting)
|
||||
- [Deprecations](#deprecations-6)
|
||||
- [Contributors](#contributors-26)
|
||||
- [Contributors](#contributors-27)
|
||||
- [v0.13.0](#v0130)
|
||||
- [Wider VFIO device support](#wider-vfio-device-support)
|
||||
- [Improved huge page support](#improved-huge-page-support)
|
||||
@@ -228,13 +234,13 @@
|
||||
- [VHD disk image support](#vhd-disk-image-support)
|
||||
- [Improved Virtio device threading](#improved-virtio-device-threading)
|
||||
- [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button)
|
||||
- [Contributors](#contributors-27)
|
||||
- [Contributors](#contributors-28)
|
||||
- [v0.12.0](#v0120)
|
||||
- [ARM64 enhancements](#arm64-enhancements)
|
||||
- [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning)
|
||||
- [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend)
|
||||
- [Enhanced "info" API](#enhanced-info-api)
|
||||
- [Contributors](#contributors-28)
|
||||
- [Contributors](#contributors-29)
|
||||
- [v0.11.0](#v0110)
|
||||
- [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block)
|
||||
- [Windows Guest Support](#windows-guest-support)
|
||||
@@ -246,15 +252,15 @@
|
||||
- [Default Log Level Changed](#default-log-level-changed)
|
||||
- [New `--balloon` Parameter Added](#new---balloon-parameter-added)
|
||||
- [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-25)
|
||||
- [Contributors](#contributors-29)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-26)
|
||||
- [Contributors](#contributors-30)
|
||||
- [v0.10.0](#v0100)
|
||||
- [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors)
|
||||
- [Memory Zones](#memory-zones)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements)
|
||||
- [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-26)
|
||||
- [Contributors](#contributors-30)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-27)
|
||||
- [Contributors](#contributors-31)
|
||||
- [v0.9.0](#v090)
|
||||
- [`io_uring` Based Block Device Support](#io_uring-based-block-device-support)
|
||||
- [Block and Network Device Statistics](#block-and-network-device-statistics)
|
||||
@@ -267,17 +273,17 @@
|
||||
- [Enhancements to ARM64 Support](#enhancements-to-arm64-support)
|
||||
- [Intel SGX Support](#intel-sgx-support)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-27)
|
||||
- [Contributors](#contributors-31)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-28)
|
||||
- [Contributors](#contributors-32)
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
|
||||
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-28)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-29)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors-32)
|
||||
- [Contributors](#contributors-33)
|
||||
- [v0.7.0](#v070)
|
||||
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
|
||||
- [Alternative `libc` Support](#alternative-libc-support)
|
||||
@@ -287,14 +293,14 @@
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-33)
|
||||
- [Contributors](#contributors-34)
|
||||
- [v0.6.0](#v060)
|
||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [PVH Boot](#pvh-boot)
|
||||
- [Contributors](#contributors-34)
|
||||
- [Contributors](#contributors-35)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -302,7 +308,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors-35)
|
||||
- [Contributors](#contributors-36)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -311,7 +317,7 @@
|
||||
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
||||
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
||||
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
||||
- [Contributors](#contributors-36)
|
||||
- [Contributors](#contributors-37)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -338,6 +344,65 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v41.0
|
||||
|
||||
This release has been tracked in our [roadmap
|
||||
project](https://github.com/orgs/cloud-hypervisor/projects/6) as iteration
|
||||
v41.0. The following user visible changes have been made:
|
||||
|
||||
### Experimental "Pvmemcontrol" Support
|
||||
|
||||
VMM support has been added for this experimental functionality (requires
|
||||
currently out of tree Linux kernel patches) to allow guests to control its
|
||||
physical memory properties to allow optimisations and security features.
|
||||
(#6318, #6467)
|
||||
|
||||
### Sandboxing With Landlock Support
|
||||
|
||||
Support for restricting the VMM process using the Linux kernel "Landlock" API
|
||||
has been added - this can be used to restrict the files (and the read/write
|
||||
permissions) that the VMM process can access. This adds another layer of
|
||||
security alongside the existing sycall filters (`seccomp`) - this can be
|
||||
enabled with `--landlock` and [fully documentated](docs/landlock.md). (#5170)
|
||||
|
||||
### Notable Performance Improvements
|
||||
|
||||
* Reduced heap allocations in `virtio-net` via the use of a cache of `Iovec`
|
||||
structures (#6636)
|
||||
* Notification suppression ("`EVENT_IDX`") support has been added to
|
||||
`virtio-block` giving a 60% improvement in single queue block throughput and
|
||||
IOPs performance (#6580)
|
||||
* Correct size used for `status` field in `virtio-block` state (#6586)
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* Avoid panic on out-of-bounds PCI MSI-X access (#6657)
|
||||
* Fix undefined behaviour on AArch64 leading to wrong optimisation on KVM API
|
||||
access (#6647)
|
||||
* Rust v1.80.0 added use of `fcntl` syscall on debug assertions so this is now
|
||||
included in the virtio-device seccomp filters for tests that use this (#6648)
|
||||
* Short reads are now handled correctly in the `virtio-vsock` device (#6621)
|
||||
* Fix undefined behaviour on TTY ioctl leading to wrong optimisation (#6568)
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our release:
|
||||
|
||||
* Alyssa Ross <hi@alyssa.is>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Changyuan Lyu <changyuanl@google.com>
|
||||
* Jinank Jain <jinankjain@microsoft.com>
|
||||
* Julian Stecklina <julian.stecklina@cyberus-technology.de>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Nuno Das Neves <nudasnev@microsoft.com>
|
||||
* Praveen K Paladugu <prapal@linux.microsoft.com>
|
||||
* Rob Bradford <rbradford@rivosinc.com>
|
||||
* Songqian Li <sionli@tencent.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
* Yuanchu Xie <yuanchu@google.com>
|
||||
* ihciah <ihciah@gmail.com>
|
||||
* wuxinyue <wuxinyue.wxy@antgroup.com>
|
||||
|
||||
# v40.0
|
||||
|
||||
This release has been tracked in our [roadmap
|
||||
|
||||
47
src/main.rs
47
src/main.rs
@@ -23,6 +23,7 @@ use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions};
|
||||
use vmm::api::http::http_api_graceful_shutdown;
|
||||
use vmm::api::ApiAction;
|
||||
use vmm::config;
|
||||
use vmm::landlock::{Landlock, LandlockError};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::signal::block_signal;
|
||||
|
||||
@@ -85,6 +86,10 @@ enum Error {
|
||||
LoggerSetup(log::SetLoggerError),
|
||||
#[error("Failed to gracefully shutdown http api: {0}")]
|
||||
HttpApiShutdown(#[source] vmm::Error),
|
||||
#[error("Failed to create Landlock object: {0}")]
|
||||
CreateLandlock(#[source] LandlockError),
|
||||
#[error("Failed to apply Landlock: {0}")]
|
||||
ApplyLandlock(#[source] LandlockError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -271,6 +276,24 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
|
||||
.num_args(1..)
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("landlock")
|
||||
.long("landlock")
|
||||
.num_args(0)
|
||||
.help(
|
||||
"enable/disable Landlock.",
|
||||
)
|
||||
.action(ArgAction::SetTrue)
|
||||
.default_value("false")
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("landlock-rules")
|
||||
.long("landlock-rules")
|
||||
.help(config::LandlockConfig::SYNTAX)
|
||||
.num_args(1..)
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("net")
|
||||
.long("net")
|
||||
@@ -499,6 +522,16 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
|
||||
.num_args(1)
|
||||
.group("vm-config"),
|
||||
);
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
let app = app.arg(
|
||||
Arg::new("pvmemcontrol")
|
||||
.long("pvmemcontrol")
|
||||
.help("Pvmemcontrol device")
|
||||
.num_args(0)
|
||||
.action(ArgAction::SetTrue)
|
||||
.group("vm-config"),
|
||||
);
|
||||
|
||||
app.arg(
|
||||
Arg::new("version")
|
||||
.short('V')
|
||||
@@ -634,6 +667,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
|
||||
|
||||
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
|
||||
let landlock_enable = cmd_arguments.get_flag("landlock");
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut event_monitor = cmd_arguments
|
||||
@@ -703,6 +737,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
vmm::start_event_monitor_thread(
|
||||
monitor,
|
||||
&seccomp_action,
|
||||
landlock_enable,
|
||||
hypervisor.hypervisor_type(),
|
||||
exit_evt.try_clone().unwrap(),
|
||||
)
|
||||
@@ -729,6 +764,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
exit_evt.try_clone().unwrap(),
|
||||
&seccomp_action,
|
||||
hypervisor,
|
||||
landlock_enable,
|
||||
)
|
||||
.map_err(Error::StartVmmThread)?;
|
||||
|
||||
@@ -776,6 +812,13 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
if landlock_enable {
|
||||
Landlock::new()
|
||||
.map_err(Error::CreateLandlock)?
|
||||
.restrict_self()
|
||||
.map_err(Error::ApplyLandlock)?;
|
||||
}
|
||||
|
||||
vmm_thread_handle
|
||||
.thread_handle
|
||||
.join()
|
||||
@@ -1021,6 +1064,8 @@ mod unit_tests {
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
pvpanic: false,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
sgx_epc: None,
|
||||
@@ -1032,6 +1077,8 @@ mod unit_tests {
|
||||
platform: None,
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
};
|
||||
|
||||
assert_eq!(expected_vm_config, result_vm_config);
|
||||
|
||||
@@ -7,10 +7,10 @@ version = "0.1.0"
|
||||
[dependencies]
|
||||
dirs = "5.0.1"
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
once_cell = "1.19.0"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
ssh2 = { version = "0.9.4", features = ["vendored-openssl"] }
|
||||
vmm-sys-util = "0.12.1"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
@@ -5149,7 +5149,11 @@ mod common_parallel {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disk_hotplug() {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
// This test runs a guest with Landlock enabled and hotplugs a new disk. As
|
||||
// the path for the hotplug disk is not pre-added to Landlock rules, this
|
||||
// the test will result in a failure.
|
||||
fn test_landlock() {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(Box::new(focal));
|
||||
|
||||
@@ -5166,6 +5170,7 @@ mod common_parallel {
|
||||
.args(["--memory", "size=512M"])
|
||||
.args(["--kernel", kernel_path.to_str().unwrap()])
|
||||
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.args(["--landlock"])
|
||||
.default_disks()
|
||||
.default_net()
|
||||
.capture_output()
|
||||
@@ -5190,6 +5195,70 @@ mod common_parallel {
|
||||
let mut blk_file_path = dirs::home_dir().unwrap();
|
||||
blk_file_path.push("workloads");
|
||||
blk_file_path.push("blk.img");
|
||||
// As the path to the hotplug disk is not pre-added, this remote
|
||||
// command will fail.
|
||||
assert!(!remote_command(
|
||||
&api_socket,
|
||||
"add-disk",
|
||||
Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()),
|
||||
));
|
||||
});
|
||||
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
handle_child_output(r, &output);
|
||||
}
|
||||
|
||||
fn _test_disk_hotplug(landlock_enabled: bool) {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(Box::new(focal));
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let kernel_path = direct_kernel_boot_path();
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let kernel_path = edk2_path();
|
||||
|
||||
let api_socket = temp_api_path(&guest.tmp_dir);
|
||||
|
||||
let mut blk_file_path = dirs::home_dir().unwrap();
|
||||
blk_file_path.push("workloads");
|
||||
blk_file_path.push("blk.img");
|
||||
|
||||
let mut cmd = GuestCommand::new(&guest);
|
||||
if landlock_enabled {
|
||||
cmd.args(["--landlock"]).args([
|
||||
"--landlock-rules",
|
||||
format!("path={:?},access=rw", blk_file_path).as_str(),
|
||||
]);
|
||||
}
|
||||
|
||||
cmd.args(["--api-socket", &api_socket])
|
||||
.args(["--cpus", "boot=1"])
|
||||
.args(["--memory", "size=512M"])
|
||||
.args(["--kernel", kernel_path.to_str().unwrap()])
|
||||
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.default_disks()
|
||||
.default_net()
|
||||
.capture_output();
|
||||
|
||||
let mut child = cmd.spawn().unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
|
||||
// Check /dev/vdc is not there
|
||||
assert_eq!(
|
||||
guest
|
||||
.ssh_command("lsblk | grep -c vdc.*16M || true")
|
||||
.unwrap()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or(1),
|
||||
0
|
||||
);
|
||||
|
||||
// Now let's add the extra disk.
|
||||
let (cmd_success, cmd_output) = remote_command_w_output(
|
||||
&api_socket,
|
||||
"add-disk",
|
||||
@@ -5306,6 +5375,17 @@ mod common_parallel {
|
||||
handle_child_output(r, &output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disk_hotplug() {
|
||||
_test_disk_hotplug(false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_disk_hotplug_with_landlock() {
|
||||
_test_disk_hotplug(true)
|
||||
}
|
||||
|
||||
fn create_loop_device(backing_file_path: &str, block_size: u32, num_retries: usize) -> String {
|
||||
const LOOP_CONFIGURE: u64 = 0x4c0a;
|
||||
const LOOP_CTL_GET_FREE: u64 = 0x4c82;
|
||||
@@ -10080,6 +10160,137 @@ mod live_migration {
|
||||
handle_child_output(Ok(()), &ovs_output);
|
||||
}
|
||||
|
||||
// This test exercises the local live-migration between two Cloud Hypervisor VMs on the
|
||||
// same host with Landlock enabled on both VMs. The test validates the following:
|
||||
// 1. The source VM is up and functional
|
||||
// 2. Ensure Landlock is enabled on source VM by hotplugging a disk. As the path for this
|
||||
// disk is not known to the source VM this step will fail.
|
||||
// 3. The 'send-migration' and 'receive-migration' command finished successfully;
|
||||
// 4. The source VM terminated gracefully after live migration;
|
||||
// 5. The destination VM is functional after live migration;
|
||||
// 6. Ensure Landlock is enabled on destination VM by hotplugging a disk. As the path for
|
||||
// this disk is not known to the destination VM this step will fail.
|
||||
fn _test_live_migration_with_landlock() {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(Box::new(focal));
|
||||
let kernel_path = direct_kernel_boot_path();
|
||||
let net_id = "net123";
|
||||
let net_params = format!(
|
||||
"id={},tap=,mac={},ip={},mask=255.255.255.0",
|
||||
net_id, guest.network.guest_mac, guest.network.host_ip
|
||||
);
|
||||
|
||||
let boot_vcpus = 2;
|
||||
let max_vcpus = 4;
|
||||
|
||||
let mut blk_file_path = dirs::home_dir().unwrap();
|
||||
blk_file_path.push("workloads");
|
||||
blk_file_path.push("blk.img");
|
||||
|
||||
let src_api_socket = temp_api_path(&guest.tmp_dir);
|
||||
let mut src_child = GuestCommand::new(&guest)
|
||||
.args([
|
||||
"--cpus",
|
||||
format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
|
||||
])
|
||||
.args(["--memory", "size=4G,shared=on"])
|
||||
.args(["--kernel", kernel_path.to_str().unwrap()])
|
||||
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.default_disks()
|
||||
.args(["--api-socket", &src_api_socket])
|
||||
.args(["--landlock"])
|
||||
.args(["--net", net_params.as_str()])
|
||||
.args([
|
||||
"--landlock-rules",
|
||||
format!("path={:?},access=rw", guest.tmp_dir.as_path()).as_str(),
|
||||
])
|
||||
.capture_output()
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
// Start the destination VM
|
||||
let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
|
||||
dest_api_socket.push_str(".dest");
|
||||
let mut dest_child = GuestCommand::new(&guest)
|
||||
.args(["--api-socket", &dest_api_socket])
|
||||
.capture_output()
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
|
||||
// Make sure the source VM is functaionl
|
||||
// Check the number of vCPUs
|
||||
assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
|
||||
|
||||
// Check the guest RAM
|
||||
assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
|
||||
|
||||
// Check Landlock is enabled by hot-plugging a disk.
|
||||
assert!(!remote_command(
|
||||
&src_api_socket,
|
||||
"add-disk",
|
||||
Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()),
|
||||
));
|
||||
|
||||
// Start the live-migration
|
||||
let migration_socket = String::from(
|
||||
guest
|
||||
.tmp_dir
|
||||
.as_path()
|
||||
.join("live-migration.sock")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
assert!(
|
||||
start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, true),
|
||||
"Unsuccessful command: 'send-migration' or 'receive-migration'."
|
||||
);
|
||||
});
|
||||
|
||||
// Check and report any errors occurred during the live-migration
|
||||
if r.is_err() {
|
||||
print_and_panic(
|
||||
src_child,
|
||||
dest_child,
|
||||
None,
|
||||
"Error occurred during live-migration",
|
||||
);
|
||||
}
|
||||
|
||||
// Check the source vm has been terminated successful (give it '3s' to settle)
|
||||
thread::sleep(std::time::Duration::new(3, 0));
|
||||
if !src_child.try_wait().unwrap().map_or(false, |s| s.success()) {
|
||||
print_and_panic(
|
||||
src_child,
|
||||
dest_child,
|
||||
None,
|
||||
"source VM was not terminated successfully.",
|
||||
);
|
||||
};
|
||||
|
||||
// Post live-migration check to make sure the destination VM is funcational
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
// Perform same checks to validate VM has been properly migrated
|
||||
assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
|
||||
assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
|
||||
});
|
||||
|
||||
// Check Landlock is enabled on destination VM by hot-plugging a disk.
|
||||
assert!(!remote_command(
|
||||
&dest_api_socket,
|
||||
"add-disk",
|
||||
Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()),
|
||||
));
|
||||
|
||||
// Clean-up the destination VM and make sure it terminated correctly
|
||||
let _ = dest_child.kill();
|
||||
let dest_output = dest_child.wait_with_output().unwrap();
|
||||
handle_child_output(r, &dest_output);
|
||||
}
|
||||
|
||||
mod live_migration_parallel {
|
||||
use super::*;
|
||||
#[test]
|
||||
@@ -10121,6 +10332,11 @@ mod live_migration {
|
||||
fn test_live_upgrade_watchdog_local() {
|
||||
_test_live_migration_watchdog(true, true)
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_live_migration_with_landlock() {
|
||||
_test_live_migration_with_landlock()
|
||||
}
|
||||
}
|
||||
|
||||
mod live_migration_sequential {
|
||||
|
||||
@@ -5,11 +5,11 @@ name = "tracer"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
once_cell = "1.19.0"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
|
||||
[features]
|
||||
tracing = []
|
||||
|
||||
@@ -10,8 +10,8 @@ block = { path = "../block" }
|
||||
clap = { version = "4.5.4", features = ["cargo", "wrap_help"] }
|
||||
env_logger = "0.11.3"
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
option_parser = { path = "../option_parser" }
|
||||
vhost = { version = "0.11.0", features = ["vhost-user-backend"] }
|
||||
vhost-user-backend = "0.15.0"
|
||||
|
||||
@@ -148,7 +148,7 @@ impl VhostUserBlkThread {
|
||||
) {
|
||||
Ok(l) => {
|
||||
len = l;
|
||||
VIRTIO_BLK_S_OK
|
||||
VIRTIO_BLK_S_OK as u8
|
||||
}
|
||||
Err(e) => {
|
||||
len = 1;
|
||||
|
||||
@@ -9,8 +9,8 @@ version = "0.1.0"
|
||||
clap = { version = "4.5.4", features = ["cargo", "wrap_help"] }
|
||||
env_logger = "0.11.3"
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
net_util = { path = "../net_util" }
|
||||
option_parser = { path = "../option_parser" }
|
||||
vhost = { version = "0.11.0", features = ["vhost-user-backend"] }
|
||||
|
||||
@@ -9,26 +9,26 @@ default = []
|
||||
sev_snp = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
arc-swap = "1.7.1"
|
||||
block = { path = "../block" }
|
||||
byteorder = "1.5.0"
|
||||
epoll = "4.3.3"
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = { path = "../pci" }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
seccompiler = "0.4.0"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.115"
|
||||
serde_with = { version = "3.7.0", default-features = false, features = [
|
||||
serde_json = "1.0.120"
|
||||
serde_with = { version = "3.9.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
serial_buffer = { path = "../serial_buffer" }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
vhost = { version = "0.11.0", features = [
|
||||
"vhost-kern",
|
||||
"vhost-user-backend",
|
||||
|
||||
@@ -40,6 +40,7 @@ use std::sync::{Arc, Barrier};
|
||||
use thiserror::Error;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use virtio_bindings::virtio_config::*;
|
||||
use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use virtio_queue::{Queue, QueueOwnedT, QueueT};
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryError};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
@@ -79,6 +80,8 @@ pub enum Error {
|
||||
QueueIterator(virtio_queue::Error),
|
||||
#[error("Failed to update request status: {0}")]
|
||||
RequestStatus(GuestMemoryError),
|
||||
#[error("Failed to enable notification: {0}")]
|
||||
QueueEnableNotification(virtio_queue::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -137,11 +140,9 @@ struct BlockEpollHandler {
|
||||
}
|
||||
|
||||
impl BlockEpollHandler {
|
||||
fn process_queue_submit(&mut self) -> Result<bool> {
|
||||
fn process_queue_submit(&mut self) -> Result<()> {
|
||||
let queue = &mut self.queue;
|
||||
|
||||
let mut used_descs = false;
|
||||
|
||||
while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) {
|
||||
let mut request = Request::parse(&mut desc_chain, self.access_platform.as_ref())
|
||||
.map_err(Error::RequestParsing)?;
|
||||
@@ -163,7 +164,9 @@ impl BlockEpollHandler {
|
||||
queue
|
||||
.add_used(desc_chain.memory(), desc_chain.head_index(), 0)
|
||||
.map_err(Error::QueueAddUsed)?;
|
||||
used_descs = true;
|
||||
queue
|
||||
.enable_notification(self.mem.memory().deref())
|
||||
.map_err(Error::QueueEnableNotification)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -215,7 +218,7 @@ impl BlockEpollHandler {
|
||||
} else {
|
||||
desc_chain
|
||||
.memory()
|
||||
.write_obj(VIRTIO_BLK_S_OK, request.status_addr)
|
||||
.write_obj(VIRTIO_BLK_S_OK as u8, request.status_addr)
|
||||
.map_err(Error::RequestStatus)?;
|
||||
|
||||
// If no asynchronous operation has been submitted, we can
|
||||
@@ -223,23 +226,34 @@ impl BlockEpollHandler {
|
||||
queue
|
||||
.add_used(desc_chain.memory(), desc_chain.head_index(), 0)
|
||||
.map_err(Error::QueueAddUsed)?;
|
||||
used_descs = true;
|
||||
queue
|
||||
.enable_notification(self.mem.memory().deref())
|
||||
.map_err(Error::QueueEnableNotification)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(used_descs)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> {
|
||||
let needs_notification = self.process_queue_submit().map_err(|e| {
|
||||
self.process_queue_submit().map_err(|e| {
|
||||
EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {:?}", e))
|
||||
})?;
|
||||
|
||||
if needs_notification {
|
||||
if self
|
||||
.queue
|
||||
.needs_notification(self.mem.memory().deref())
|
||||
.map_err(|e| {
|
||||
EpollHelperError::HandleEvent(anyhow!(
|
||||
"Failed to check needs_notification: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
{
|
||||
self.signal_used_queue().map_err(|e| {
|
||||
EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {:?}", e))
|
||||
})?
|
||||
};
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -265,8 +279,7 @@ impl BlockEpollHandler {
|
||||
Err(Error::MissingEntryRequestList)
|
||||
}
|
||||
|
||||
fn process_queue_complete(&mut self) -> Result<bool> {
|
||||
let mut used_descs = false;
|
||||
fn process_queue_complete(&mut self) -> Result<()> {
|
||||
let mem = self.mem.memory();
|
||||
let mut read_bytes = Wrapping(0);
|
||||
let mut write_bytes = Wrapping(0);
|
||||
@@ -361,7 +374,7 @@ impl BlockEpollHandler {
|
||||
.write_latency_avg
|
||||
.store(write_avg, Ordering::Relaxed);
|
||||
|
||||
(VIRTIO_BLK_S_OK, result as u32)
|
||||
(VIRTIO_BLK_S_OK as u8, result as u32)
|
||||
} else {
|
||||
error!(
|
||||
"Request failed: {:x?} {:?}",
|
||||
@@ -379,7 +392,9 @@ impl BlockEpollHandler {
|
||||
queue
|
||||
.add_used(mem.deref(), desc_index, len)
|
||||
.map_err(Error::QueueAddUsed)?;
|
||||
used_descs = true;
|
||||
queue
|
||||
.enable_notification(mem.deref())
|
||||
.map_err(Error::QueueEnableNotification)?;
|
||||
}
|
||||
|
||||
self.counters
|
||||
@@ -396,7 +411,7 @@ impl BlockEpollHandler {
|
||||
.read_ops
|
||||
.fetch_add(read_ops.0, Ordering::AcqRel);
|
||||
|
||||
Ok(used_descs)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
@@ -487,20 +502,19 @@ impl EpollHelperHandler for BlockEpollHandler {
|
||||
EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e))
|
||||
})?;
|
||||
|
||||
let needs_notification = self.process_queue_complete().map_err(|e| {
|
||||
self.process_queue_complete().map_err(|e| {
|
||||
EpollHelperError::HandleEvent(anyhow!(
|
||||
"Failed to process queue (complete): {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
if needs_notification {
|
||||
self.signal_used_queue().map_err(|e| {
|
||||
EpollHelperError::HandleEvent(anyhow!(
|
||||
"Failed to signal used queue: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
let rate_limit_reached =
|
||||
self.rate_limiter.as_ref().map_or(false, |r| r.is_blocked());
|
||||
|
||||
// Process the queue only when the rate limit is not reached
|
||||
if !rate_limit_reached {
|
||||
self.process_queue_submit_and_signal()?
|
||||
}
|
||||
}
|
||||
RATE_LIMITER_EVENT => {
|
||||
@@ -606,7 +620,8 @@ impl Block {
|
||||
| (1u64 << VIRTIO_BLK_F_FLUSH)
|
||||
| (1u64 << VIRTIO_BLK_F_CONFIG_WCE)
|
||||
| (1u64 << VIRTIO_BLK_F_BLK_SIZE)
|
||||
| (1u64 << VIRTIO_BLK_F_TOPOLOGY);
|
||||
| (1u64 << VIRTIO_BLK_F_TOPOLOGY)
|
||||
| (1u64 << VIRTIO_RING_F_EVENT_IDX);
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
@@ -779,8 +794,12 @@ impl VirtioDevice for Block {
|
||||
self.update_writeback();
|
||||
|
||||
let mut epoll_threads = Vec::new();
|
||||
let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into());
|
||||
|
||||
for i in 0..queues.len() {
|
||||
let (_, queue, queue_evt) = queues.remove(0);
|
||||
let (_, mut queue, queue_evt) = queues.remove(0);
|
||||
queue.set_event_idx(event_idx);
|
||||
|
||||
let queue_size = queue.size();
|
||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||
let queue_idx = i as u16;
|
||||
|
||||
@@ -608,11 +608,11 @@ fn get_win_size(tty: &dyn AsRawFd) -> (u16, u16) {
|
||||
xpixel: u16,
|
||||
ypixel: u16,
|
||||
}
|
||||
let ws: WindowSize = WindowSize::default();
|
||||
let mut ws: WindowSize = WindowSize::default();
|
||||
|
||||
// SAFETY: FFI call with correct arguments
|
||||
unsafe {
|
||||
libc::ioctl(tty.as_raw_fd(), TIOCGWINSZ, &ws);
|
||||
libc::ioctl(tty.as_raw_fd(), TIOCGWINSZ, &mut ws);
|
||||
}
|
||||
|
||||
(ws.cols, ws.rows)
|
||||
|
||||
@@ -352,9 +352,8 @@ impl Request {
|
||||
let desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("Missing head descriptor");
|
||||
e
|
||||
})?;
|
||||
|
||||
// The descriptor contains the request type which MUST be readable.
|
||||
|
||||
@@ -209,6 +209,10 @@ fn virtio_vsock_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
|
||||
(libc::SYS_ioctl, create_vsock_ioctl_seccomp_rule()),
|
||||
(libc::SYS_recvfrom, vec![]),
|
||||
(libc::SYS_socket, vec![]),
|
||||
// If debug_assertions is enabled, closing a file first checks
|
||||
// whether the FD is valid with fcntl.
|
||||
#[cfg(debug_assertions)]
|
||||
(libc::SYS_fcntl, vec![]),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -495,11 +495,10 @@ impl VsockMuxer {
|
||||
const MIN_COMMAND_LEN: usize = 10;
|
||||
|
||||
// Bring in the minimum number of bytes that we should be able to read.
|
||||
if command.len < MIN_COMMAND_LEN {
|
||||
command.len += stream
|
||||
.read(&mut command.buf[command.len..MIN_COMMAND_LEN])
|
||||
.map_err(Error::UnixRead)?;
|
||||
}
|
||||
stream
|
||||
.read_exact(&mut command.buf[command.len..MIN_COMMAND_LEN])
|
||||
.map_err(Error::UnixRead)?;
|
||||
command.len = MIN_COMMAND_LEN;
|
||||
|
||||
// Now, finish reading the destination port number, by bringing in one byte at a time,
|
||||
// until we reach an EOL terminator (or our buffer space runs out). Yeah, not
|
||||
@@ -510,7 +509,6 @@ impl VsockMuxer {
|
||||
.map_err(Error::UnixRead)?;
|
||||
}
|
||||
|
||||
let _ = command;
|
||||
let command = partial_command_map.remove(&stream.as_raw_fd()).unwrap();
|
||||
|
||||
let mut word_iter = std::str::from_utf8(&command.buf[..command.len])
|
||||
|
||||
@@ -5,7 +5,7 @@ name = "vm-allocator"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
vm-memory = "0.14.1"
|
||||
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
|
||||
@@ -10,10 +10,10 @@ kvm = ["vfio-ioctls/kvm"]
|
||||
mshv = ["vfio-ioctls/mshv"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vm-memory = { version = "0.14.1", features = ["backend-mmap"] }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -26,6 +26,31 @@ pub trait BusDevice: Send {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait BusDeviceSync: Send + Sync {
|
||||
/// Reads at `offset` from this device
|
||||
fn read(&self, base: u64, offset: u64, data: &mut [u8]) {}
|
||||
/// Writes at `offset` into this device
|
||||
fn write(&self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BusDevice> BusDeviceSync for Mutex<B> {
|
||||
/// Reads at `offset` from this device
|
||||
fn read(&self, base: u64, offset: u64, data: &mut [u8]) {
|
||||
self.lock()
|
||||
.expect("Failed to acquire device lock")
|
||||
.read(base, offset, data)
|
||||
}
|
||||
/// Writes at `offset` into this device
|
||||
fn write(&self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
self.lock()
|
||||
.expect("Failed to acquire device lock")
|
||||
.write(base, offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The insertion failed because the new device overlapped with an old device.
|
||||
@@ -95,7 +120,7 @@ impl PartialOrd for BusRange {
|
||||
/// only restriction is that no two devices can overlap in this address space.
|
||||
#[derive(Default)]
|
||||
pub struct Bus {
|
||||
devices: RwLock<BTreeMap<BusRange, Weak<Mutex<dyn BusDevice>>>>,
|
||||
devices: RwLock<BTreeMap<BusRange, Weak<dyn BusDeviceSync>>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
@@ -106,7 +131,7 @@ impl Bus {
|
||||
}
|
||||
}
|
||||
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, Arc<Mutex<dyn BusDevice>>)> {
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, Arc<dyn BusDeviceSync>)> {
|
||||
let devices = self.devices.read().unwrap();
|
||||
let (range, dev) = devices
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
@@ -115,7 +140,7 @@ impl Bus {
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, Arc<Mutex<dyn BusDevice>>)> {
|
||||
fn resolve(&self, addr: u64) -> Option<(u64, u64, Arc<dyn BusDeviceSync>)> {
|
||||
if let Some((range, dev)) = self.first_before(addr) {
|
||||
let offset = addr - range.base;
|
||||
if offset < range.len {
|
||||
@@ -125,8 +150,7 @@ impl Bus {
|
||||
None
|
||||
}
|
||||
|
||||
/// Puts the given device at the given address space.
|
||||
pub fn insert(&self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
pub fn insert(&self, device: Arc<dyn BusDeviceSync>, base: u64, len: u64) -> Result<()> {
|
||||
if len == 0 {
|
||||
return Err(Error::ZeroSizedRange);
|
||||
}
|
||||
@@ -171,7 +195,7 @@ impl Bus {
|
||||
}
|
||||
|
||||
/// Removes all entries referencing the given device.
|
||||
pub fn remove_by_device(&self, device: &Arc<Mutex<dyn BusDevice>>) -> Result<()> {
|
||||
pub fn remove_by_device(&self, device: &Arc<dyn BusDeviceSync>) -> Result<()> {
|
||||
let mut device_list = self.devices.write().unwrap();
|
||||
let mut remove_key_list = Vec::new();
|
||||
|
||||
@@ -216,9 +240,7 @@ impl Bus {
|
||||
pub fn read(&self, addr: u64, data: &mut [u8]) -> Result<()> {
|
||||
if let Some((base, offset, dev)) = self.resolve(addr) {
|
||||
// OK to unwrap as lock() failing is a serious error condition and should panic.
|
||||
dev.lock()
|
||||
.expect("Failed to acquire device lock")
|
||||
.read(base, offset, data);
|
||||
dev.read(base, offset, data);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::MissingAddressRange)
|
||||
@@ -231,10 +253,7 @@ impl Bus {
|
||||
pub fn write(&self, addr: u64, data: &[u8]) -> Result<Option<Arc<Barrier>>> {
|
||||
if let Some((base, offset, dev)) = self.resolve(addr) {
|
||||
// OK to unwrap as lock() failing is a serious error condition and should panic.
|
||||
Ok(dev
|
||||
.lock()
|
||||
.expect("Failed to acquire device lock")
|
||||
.write(base, offset, data))
|
||||
Ok(dev.write(base, offset, data))
|
||||
} else {
|
||||
Err(Error::MissingAddressRange)
|
||||
}
|
||||
@@ -246,17 +265,17 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
struct DummyDevice;
|
||||
impl BusDevice for DummyDevice {}
|
||||
impl BusDeviceSync for DummyDevice {}
|
||||
|
||||
struct ConstantDevice;
|
||||
impl BusDevice for ConstantDevice {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
impl BusDeviceSync for ConstantDevice {
|
||||
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
for (i, v) in data.iter_mut().enumerate() {
|
||||
*v = (offset as u8) + (i as u8);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
for (i, v) in data.iter().enumerate() {
|
||||
assert_eq!(*v, (offset as u8) + (i as u8))
|
||||
}
|
||||
@@ -268,7 +287,7 @@ mod tests {
|
||||
#[test]
|
||||
fn bus_insert() {
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||
let dummy = Arc::new(DummyDevice);
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
|
||||
@@ -290,7 +309,7 @@ mod tests {
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn bus_read_write() {
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||
let dummy = Arc::new(DummyDevice);
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.read(0x10, &mut [0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.write(0x10, &[0, 0, 0, 0]).is_ok());
|
||||
@@ -308,7 +327,7 @@ mod tests {
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn bus_read_write_values() {
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(ConstantDevice));
|
||||
let dummy = Arc::new(ConstantDevice);
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
|
||||
let mut values = [0, 1, 2, 3];
|
||||
@@ -334,7 +353,7 @@ mod tests {
|
||||
|
||||
let bus = Bus::new();
|
||||
let mut data = [1, 2, 3, 4];
|
||||
let device = Arc::new(Mutex::new(DummyDevice));
|
||||
let device = Arc::new(DummyDevice);
|
||||
assert!(bus.insert(device.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.write(0x10, &data).is_ok());
|
||||
assert!(bus.read(0x10, &mut data).is_ok());
|
||||
|
||||
@@ -9,7 +9,7 @@ mod bus;
|
||||
pub mod dma_mapping;
|
||||
pub mod interrupt;
|
||||
|
||||
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
||||
pub use self::bus::{Bus, BusDevice, BusDeviceSync, Error as BusError};
|
||||
|
||||
/// Type of Message Signalled Interrupt
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -5,10 +5,10 @@ name = "vm-migration"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.115"
|
||||
thiserror = "1.0.60"
|
||||
serde_json = "1.0.120"
|
||||
thiserror = "1.0.62"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-mmap",
|
||||
|
||||
@@ -190,6 +190,22 @@ impl Response {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn ok_or_abandon<T>(
|
||||
self,
|
||||
fd: &mut T,
|
||||
error: MigratableError,
|
||||
) -> Result<Response, MigratableError>
|
||||
where
|
||||
T: Read + Write,
|
||||
{
|
||||
if self.status != Status::Ok {
|
||||
Request::abandon().write_to(fd)?;
|
||||
Response::read_from(fd)?;
|
||||
return Err(error);
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||
fd.write_all(Self::as_slice(self))
|
||||
.map_err(MigratableError::MigrateSocket)
|
||||
|
||||
@@ -8,7 +8,7 @@ version = "0.1.0"
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.21"
|
||||
log = "0.4.22"
|
||||
virtio-queue = "0.12.0"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
|
||||
@@ -7,22 +7,29 @@ version = "0.1.0"
|
||||
[features]
|
||||
dbus_api = ["blocking", "futures", "zbus"]
|
||||
default = []
|
||||
dhat-heap = ["dhat"] # For heap profiling
|
||||
dhat-heap = ["dhat"] # For heap profiling
|
||||
guest_debug = ["gdbstub", "gdbstub_arch", "kvm"]
|
||||
igvm = ["dep:igvm", "hex", "igvm_defs", "mshv-bindings", "range_map_vec"]
|
||||
io_uring = ["block/io_uring"]
|
||||
kvm = ["hypervisor/kvm", "pci/kvm", "vfio-ioctls/kvm", "vm-device/kvm"]
|
||||
kvm = [
|
||||
"arch/kvm",
|
||||
"hypervisor/kvm",
|
||||
"pci/kvm",
|
||||
"vfio-ioctls/kvm",
|
||||
"vm-device/kvm",
|
||||
]
|
||||
mshv = ["hypervisor/mshv", "pci/mshv", "vfio-ioctls/mshv", "vm-device/mshv"]
|
||||
pvmemcontrol = ["devices/pvmemcontrol"]
|
||||
sev_snp = ["arch/sev_snp", "hypervisor/sev_snp", "virtio-devices/sev_snp"]
|
||||
tdx = ["arch/tdx", "hypervisor/tdx"]
|
||||
tracing = ["tracer/tracing"]
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
arc-swap = "1.7.1"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "2.5.0"
|
||||
bitflags = "2.6.0"
|
||||
block = { path = "../block" }
|
||||
blocking = { version = "1.6.1", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
@@ -37,11 +44,12 @@ gdbstub = { version = "0.7.1", optional = true }
|
||||
gdbstub_arch = { version = "0.3.0", optional = true }
|
||||
hex = { version = "0.4.3", optional = true }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
igvm = { version = "0.3.1", optional = true }
|
||||
igvm = { version = "0.3.3", optional = true }
|
||||
igvm_defs = { version = "0.3.1", optional = true }
|
||||
libc = "0.2.153"
|
||||
landlock = "0.4.0"
|
||||
libc = "0.2.155"
|
||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.21"
|
||||
log = "0.4.22"
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", features = [
|
||||
"fam-wrappers",
|
||||
@@ -55,10 +63,10 @@ range_map_vec = { version = "0.2.0", optional = true }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
seccompiler = "0.4.0"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
serial_buffer = { path = "../serial_buffer" }
|
||||
signal-hook = "0.3.17"
|
||||
thiserror = "1.0.60"
|
||||
thiserror = "1.0.62"
|
||||
tracer = { path = "../tracer" }
|
||||
uuid = "1.8.0"
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
@@ -76,4 +84,4 @@ vm-migration = { path = "../vm-migration" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
||||
zbus = { version = "4.1.2", optional = true }
|
||||
zerocopy = { version = "0.7.32", features = ["alloc", "derive"] }
|
||||
zerocopy = { version = "0.7.35", features = ["alloc", "derive"] }
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::api::{
|
||||
VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
|
||||
VmSendMigration, VmShutdown, VmSnapshot,
|
||||
};
|
||||
use crate::landlock::Landlock;
|
||||
use crate::seccomp_filters::{get_seccomp_filter, Thread};
|
||||
use crate::{Error as VmmError, Result};
|
||||
use core::fmt;
|
||||
@@ -303,6 +304,7 @@ fn start_http_thread(
|
||||
seccomp_action: &SeccompAction,
|
||||
exit_evt: EventFd,
|
||||
hypervisor_type: HypervisorType,
|
||||
landlock_enable: bool,
|
||||
) -> Result<HttpApiHandle> {
|
||||
// Retrieve seccomp filter for API thread
|
||||
let api_seccomp_filter = get_seccomp_filter(seccomp_action, Thread::HttpApi, hypervisor_type)
|
||||
@@ -329,6 +331,18 @@ fn start_http_thread(
|
||||
})?;
|
||||
}
|
||||
|
||||
if landlock_enable {
|
||||
Landlock::new()
|
||||
.map_err(VmmError::CreateLandlock)?
|
||||
.restrict_self()
|
||||
.map_err(VmmError::ApplyLandlock)
|
||||
.map_err(|e| {
|
||||
error!("Error applying landlock to http-server thread: {:?}", e);
|
||||
exit_evt.write(1).ok();
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
std::panic::catch_unwind(AssertUnwindSafe(move || {
|
||||
server.start_server().unwrap();
|
||||
loop {
|
||||
@@ -375,6 +389,7 @@ pub fn start_http_path_thread(
|
||||
seccomp_action: &SeccompAction,
|
||||
exit_evt: EventFd,
|
||||
hypervisor_type: HypervisorType,
|
||||
landlock_enable: bool,
|
||||
) -> Result<HttpApiHandle> {
|
||||
let socket_path = PathBuf::from(path);
|
||||
let socket_fd = UnixListener::bind(socket_path).map_err(VmmError::CreateApiServerSocket)?;
|
||||
@@ -389,6 +404,7 @@ pub fn start_http_path_thread(
|
||||
seccomp_action,
|
||||
exit_evt,
|
||||
hypervisor_type,
|
||||
landlock_enable,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -399,6 +415,7 @@ pub fn start_http_fd_thread(
|
||||
seccomp_action: &SeccompAction,
|
||||
exit_evt: EventFd,
|
||||
hypervisor_type: HypervisorType,
|
||||
landlock_enable: bool,
|
||||
) -> Result<HttpApiHandle> {
|
||||
// SAFETY: Valid FD
|
||||
let server = unsafe { HttpServer::new_from_fd(fd) }.map_err(VmmError::CreateApiServer)?;
|
||||
@@ -409,6 +426,7 @@ pub fn start_http_fd_thread(
|
||||
seccomp_action,
|
||||
exit_evt,
|
||||
hypervisor_type,
|
||||
landlock_enable,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -626,6 +626,13 @@ components:
|
||||
$ref: "#/components/schemas/PlatformConfig"
|
||||
tpm:
|
||||
$ref: "#/components/schemas/TpmConfig"
|
||||
landlock_enable:
|
||||
type: boolean
|
||||
default: false
|
||||
landlock_rules:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/LandlockConfig"
|
||||
description: Virtual machine configuration
|
||||
|
||||
CpuAffinity:
|
||||
@@ -1261,3 +1268,14 @@ components:
|
||||
properties:
|
||||
socket:
|
||||
type: string
|
||||
|
||||
LandlockConfig:
|
||||
required:
|
||||
- path
|
||||
- access
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
access:
|
||||
type: string
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::landlock::LandlockAccess;
|
||||
pub use crate::vm_config::*;
|
||||
use clap::ArgMatches;
|
||||
use option_parser::{
|
||||
@@ -110,6 +111,10 @@ pub enum Error {
|
||||
ParseTpm(OptionParserError),
|
||||
/// Missing path for TPM device
|
||||
ParseTpmPathMissing,
|
||||
/// Error parsing Landlock rules
|
||||
ParseLandlockRules(OptionParserError),
|
||||
/// Missing fields in Landlock rules
|
||||
ParseLandlockMissingFields,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
@@ -205,6 +210,10 @@ pub enum ValidationError {
|
||||
RestoreMissingRequiredNetId(String),
|
||||
/// Number of FDs passed during Restore are incorrect to the NetConfig
|
||||
RestoreNetFdCountMismatch(String, usize, usize),
|
||||
/// Path provided in landlock-rules doesn't exist
|
||||
LandlockPathDoesNotExist(PathBuf),
|
||||
/// Access provided in landlock-rules in invalid
|
||||
InvalidLandlockAccess(String),
|
||||
}
|
||||
|
||||
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
||||
@@ -356,6 +365,16 @@ impl fmt::Display for ValidationError {
|
||||
"Number of Net FDs passed for '{s}' during Restore: {u1}. Expected: {u2}"
|
||||
)
|
||||
}
|
||||
LandlockPathDoesNotExist(s) => {
|
||||
write!(
|
||||
f,
|
||||
"Path {:?} provided in landlock-rules does not exist",
|
||||
s.as_path()
|
||||
)
|
||||
}
|
||||
InvalidLandlockAccess(s) => {
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,6 +440,11 @@ impl fmt::Display for Error {
|
||||
ParseVdpaPathMissing => write!(f, "Error parsing --vdpa: path missing"),
|
||||
ParseTpm(o) => write!(f, "Error parsing --tpm: {o}"),
|
||||
ParseTpmPathMissing => write!(f, "Error parsing --tpm: path missing"),
|
||||
ParseLandlockRules(o) => write!(f, "Error parsing --landlock-rules: {o}"),
|
||||
ParseLandlockMissingFields => write!(
|
||||
f,
|
||||
"Error parsing --landlock-rules: path/access field missing"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,6 +482,8 @@ pub struct VmParams<'a> {
|
||||
pub user_devices: Option<Vec<&'a str>>,
|
||||
pub vdpa: Option<Vec<&'a str>>,
|
||||
pub vsock: Option<&'a str>,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pub pvmemcontrol: bool,
|
||||
pub pvpanic: bool,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub sgx_epc: Option<Vec<&'a str>>,
|
||||
@@ -472,6 +498,8 @@ pub struct VmParams<'a> {
|
||||
pub igvm: Option<&'a str>,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
pub host_data: Option<&'a str>,
|
||||
pub landlock_enable: bool,
|
||||
pub landlock_rules: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> VmParams<'a> {
|
||||
@@ -517,6 +545,8 @@ impl<'a> VmParams<'a> {
|
||||
.get_many::<String>("vdpa")
|
||||
.map(|x| x.map(|y| y as &str).collect());
|
||||
let vsock: Option<&str> = args.get_one::<String>("vsock").map(|x| x as &str);
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
let pvmemcontrol = args.get_flag("pvmemcontrol");
|
||||
let pvpanic = args.get_flag("pvpanic");
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let sgx_epc: Option<Vec<&str>> = args
|
||||
@@ -537,6 +567,11 @@ impl<'a> VmParams<'a> {
|
||||
let igvm = args.get_one::<String>("igvm").map(|x| x as &str);
|
||||
#[cfg(feature = "sev_snp")]
|
||||
let host_data = args.get_one::<String>("host-data").map(|x| x as &str);
|
||||
let landlock_enable = args.get_flag("landlock");
|
||||
let landlock_rules: Option<Vec<&str>> = args
|
||||
.get_many::<String>("landlock-rules")
|
||||
.map(|x| x.map(|y| y as &str).collect());
|
||||
|
||||
VmParams {
|
||||
cpus,
|
||||
memory,
|
||||
@@ -560,6 +595,8 @@ impl<'a> VmParams<'a> {
|
||||
user_devices,
|
||||
vdpa,
|
||||
vsock,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol,
|
||||
pvpanic,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
sgx_epc,
|
||||
@@ -574,6 +611,8 @@ impl<'a> VmParams<'a> {
|
||||
igvm,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
host_data,
|
||||
landlock_enable,
|
||||
landlock_rules,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2301,6 +2340,45 @@ impl TpmConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl LandlockConfig {
|
||||
pub const SYNTAX: &'static str = "Landlock parameters \
|
||||
\"path=<path/to/{file/dir}>,access=[rw]\"";
|
||||
|
||||
pub fn parse(landlock_rule: &str) -> Result<Self> {
|
||||
let mut parser = OptionParser::new();
|
||||
parser.add("path").add("access");
|
||||
parser
|
||||
.parse(landlock_rule)
|
||||
.map_err(Error::ParseLandlockRules)?;
|
||||
|
||||
let path = parser
|
||||
.get("path")
|
||||
.map(PathBuf::from)
|
||||
.ok_or(Error::ParseLandlockMissingFields)?;
|
||||
|
||||
let access = parser
|
||||
.get("access")
|
||||
.ok_or(Error::ParseLandlockMissingFields)?;
|
||||
|
||||
if access.chars().count() > 2 {
|
||||
return Err(Error::ParseLandlockRules(OptionParserError::InvalidValue(
|
||||
access.to_string(),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(LandlockConfig { path, access })
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> ValidationResult<()> {
|
||||
if !self.path.exists() {
|
||||
return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
|
||||
}
|
||||
LandlockAccess::try_from(self.access.as_str())
|
||||
.map_err(|e| ValidationError::InvalidLandlockAccess(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VmConfig {
|
||||
fn validate_identifier(
|
||||
id_list: &mut BTreeSet<String>,
|
||||
@@ -2653,6 +2731,12 @@ impl VmConfig {
|
||||
.map(|p| p.iommu_segments.is_some())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(landlock_rules) = &self.landlock_rules {
|
||||
for landlock_rule in landlock_rules {
|
||||
landlock_rule.validate()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(id_list)
|
||||
}
|
||||
|
||||
@@ -2694,6 +2778,11 @@ impl VmConfig {
|
||||
balloon = Some(BalloonConfig::parse(balloon_params)?);
|
||||
}
|
||||
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
let pvmemcontrol: Option<PvmemcontrolConfig> = vm_params
|
||||
.pvmemcontrol
|
||||
.then_some(PvmemcontrolConfig::default());
|
||||
|
||||
let mut fs: Option<Vec<FsConfig>> = None;
|
||||
if let Some(fs_list) = &vm_params.fs {
|
||||
let mut fs_config_list = Vec::new();
|
||||
@@ -2823,6 +2912,16 @@ impl VmConfig {
|
||||
#[cfg(feature = "guest_debug")]
|
||||
let gdb = vm_params.gdb;
|
||||
|
||||
let mut landlock_rules: Option<Vec<LandlockConfig>> = None;
|
||||
if let Some(ll_rules) = vm_params.landlock_rules {
|
||||
landlock_rules = Some(
|
||||
ll_rules
|
||||
.iter()
|
||||
.map(|rule| LandlockConfig::parse(rule))
|
||||
.collect::<Result<Vec<LandlockConfig>>>()?,
|
||||
);
|
||||
}
|
||||
|
||||
let mut config = VmConfig {
|
||||
cpus: CpusConfig::parse(vm_params.cpus)?,
|
||||
memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
|
||||
@@ -2842,6 +2941,8 @@ impl VmConfig {
|
||||
user_devices,
|
||||
vdpa,
|
||||
vsock,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol,
|
||||
pvpanic: vm_params.pvpanic,
|
||||
iommu: false, // updated in VmConfig::validate()
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -2854,6 +2955,8 @@ impl VmConfig {
|
||||
platform,
|
||||
tpm,
|
||||
preserved_fds: None,
|
||||
landlock_enable: vm_params.landlock_enable,
|
||||
landlock_rules,
|
||||
};
|
||||
config.validate().map_err(Error::Validation)?;
|
||||
Ok(config)
|
||||
@@ -2959,6 +3062,8 @@ impl Clone for VmConfig {
|
||||
net: self.net.clone(),
|
||||
rng: self.rng.clone(),
|
||||
balloon: self.balloon.clone(),
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: self.pvmemcontrol.clone(),
|
||||
fs: self.fs.clone(),
|
||||
pmem: self.pmem.clone(),
|
||||
serial: self.serial.clone(),
|
||||
@@ -2980,6 +3085,7 @@ impl Clone for VmConfig {
|
||||
.as_ref()
|
||||
// SAFETY: FFI call with valid FDs
|
||||
.map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()),
|
||||
landlock_rules: self.landlock_rules.clone(),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
@@ -3747,6 +3853,8 @@ mod tests {
|
||||
user_devices: None,
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
pvpanic: false,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -3778,6 +3886,8 @@ mod tests {
|
||||
..net_fixture()
|
||||
},
|
||||
]),
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
};
|
||||
|
||||
let valid_config = RestoreConfig {
|
||||
@@ -3954,6 +4064,8 @@ mod tests {
|
||||
user_devices: None,
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
pvpanic: false,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -3966,6 +4078,8 @@ mod tests {
|
||||
platform: None,
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
};
|
||||
|
||||
assert!(valid_config.validate().is_ok());
|
||||
@@ -4523,4 +4637,20 @@ mod tests {
|
||||
}
|
||||
let _still_valid_config = still_valid_config.clone();
|
||||
}
|
||||
#[test]
|
||||
fn test_landlock_parsing() -> Result<()> {
|
||||
// should not be empty
|
||||
assert!(LandlockConfig::parse("").is_err());
|
||||
// access should not be empty
|
||||
assert!(LandlockConfig::parse("path=/dir/path1").is_err());
|
||||
assert!(LandlockConfig::parse("path=/dir/path1,access=rwr").is_err());
|
||||
assert_eq!(
|
||||
LandlockConfig::parse("path=/dir/path1,access=rw")?,
|
||||
LandlockConfig {
|
||||
path: PathBuf::from("/dir/path1"),
|
||||
access: "rw".to_string(),
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ use devices::gic;
|
||||
use devices::ioapic;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use devices::legacy::Pl011;
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice};
|
||||
use devices::{
|
||||
interrupt_controller, interrupt_controller::InterruptController, AcpiNotificationFlags,
|
||||
};
|
||||
@@ -89,7 +91,7 @@ use vm_device::dma_mapping::ExternalDmaMapping;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, LegacyIrqGroupConfig, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_device::{Bus, BusDevice, Resource};
|
||||
use vm_device::{Bus, BusDevice, BusDeviceSync, Resource};
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::GuestMemoryRegion;
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize, MmapRegion};
|
||||
@@ -118,6 +120,8 @@ const DEBUGCON_DEVICE_NAME: &str = "__debug_console";
|
||||
const GPIO_DEVICE_NAME: &str = "__gpio";
|
||||
const RNG_DEVICE_NAME: &str = "__rng";
|
||||
const IOMMU_DEVICE_NAME: &str = "__iommu";
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
const PVMEMCONTROL_DEVICE_NAME: &str = "__pvmemcontrol";
|
||||
const BALLOON_DEVICE_NAME: &str = "__balloon";
|
||||
const CONSOLE_DEVICE_NAME: &str = "__console";
|
||||
const PVPANIC_DEVICE_NAME: &str = "__pvpanic";
|
||||
@@ -195,6 +199,10 @@ pub enum DeviceManagerError {
|
||||
/// Cannot create virtio-balloon device
|
||||
CreateVirtioBalloon(io::Error),
|
||||
|
||||
/// Cannot create pvmemcontrol device
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
CreatePvmemcontrol(io::Error),
|
||||
|
||||
/// Cannot create virtio-watchdog device
|
||||
CreateVirtioWatchdog(io::Error),
|
||||
|
||||
@@ -773,6 +781,39 @@ pub struct AcpiPlatformAddresses {
|
||||
pub sleep_status_reg_address: Option<GenericAddress>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "mshv", feature = "sev_snp"))]
|
||||
struct SevSnpPageAccessProxy {
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "mshv", feature = "sev_snp"))]
|
||||
impl std::fmt::Debug for SevSnpPageAccessProxy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SNP Page access proxy")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "mshv", feature = "sev_snp"))]
|
||||
impl SevSnpPageAccessProxy {
|
||||
fn new(vm: Arc<dyn hypervisor::Vm>) -> SevSnpPageAccessProxy {
|
||||
SevSnpPageAccessProxy { vm }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "mshv", feature = "sev_snp"))]
|
||||
impl AccessPlatform for SevSnpPageAccessProxy {
|
||||
fn translate_gpa(&self, base: u64, _size: u64) -> std::result::Result<u64, std::io::Error> {
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
fn translate_gva(&self, base: u64, size: u64) -> std::result::Result<u64, std::io::Error> {
|
||||
self.vm
|
||||
.gain_page_access(base, size as u32)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(base)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeviceManager {
|
||||
// Manage address space related to devices
|
||||
address_manager: Arc<AddressManager>,
|
||||
@@ -818,7 +859,7 @@ pub struct DeviceManager {
|
||||
// Let the DeviceManager keep strong references to the BusDevice devices.
|
||||
// This allows the IO and MMIO buses to be provided with Weak references,
|
||||
// which prevents cyclic dependencies.
|
||||
bus_devices: Vec<Arc<Mutex<dyn BusDevice>>>,
|
||||
bus_devices: Vec<Arc<dyn BusDeviceSync>>,
|
||||
|
||||
// Counter to keep track of the consumed device IDs.
|
||||
device_id_cnt: Wrapping<usize>,
|
||||
@@ -886,6 +927,12 @@ pub struct DeviceManager {
|
||||
// GPIO device for AArch64
|
||||
gpio_device: Option<Arc<Mutex<devices::legacy::Gpio>>>,
|
||||
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol_devices: Option<(
|
||||
Arc<PvmemcontrolBusDevice>,
|
||||
Arc<Mutex<PvmemcontrolPciDevice>>,
|
||||
)>,
|
||||
|
||||
// pvpanic device
|
||||
pvpanic_device: Option<Arc<Mutex<devices::PvPanicDevice>>>,
|
||||
|
||||
@@ -1165,6 +1212,8 @@ impl DeviceManager {
|
||||
virtio_mem_devices: Vec::new(),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
gpio_device: None,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol_devices: None,
|
||||
pvpanic_device: None,
|
||||
force_iommu,
|
||||
io_uring_supported: None,
|
||||
@@ -1183,7 +1232,7 @@ impl DeviceManager {
|
||||
address_manager
|
||||
.mmio_bus
|
||||
.insert(
|
||||
Arc::clone(&device_manager) as Arc<Mutex<dyn BusDevice>>,
|
||||
Arc::clone(&device_manager) as Arc<dyn BusDeviceSync>,
|
||||
acpi_address.0,
|
||||
DEVICE_MANAGER_ACPI_SIZE as u64,
|
||||
)
|
||||
@@ -1226,7 +1275,7 @@ impl DeviceManager {
|
||||
self.address_manager
|
||||
.mmio_bus
|
||||
.insert(
|
||||
Arc::clone(&self.memory_manager) as Arc<Mutex<dyn BusDevice>>,
|
||||
Arc::clone(&self.memory_manager) as Arc<dyn BusDeviceSync>,
|
||||
acpi_address.0,
|
||||
MEMORY_MANAGER_ACPI_SIZE as u64,
|
||||
)
|
||||
@@ -1268,7 +1317,7 @@ impl DeviceManager {
|
||||
if let Some(tpm) = self.config.clone().lock().unwrap().tpm.as_ref() {
|
||||
let tpm_dev = self.add_tpm_device(tpm.socket.clone())?;
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&tpm_dev) as Arc<Mutex<dyn BusDevice>>)
|
||||
.push(Arc::clone(&tpm_dev) as Arc<dyn BusDeviceSync>)
|
||||
}
|
||||
self.legacy_interrupt_manager = Some(legacy_interrupt_manager);
|
||||
|
||||
@@ -1278,6 +1327,17 @@ impl DeviceManager {
|
||||
|
||||
self.virtio_devices = virtio_devices;
|
||||
|
||||
// Add pvmemcontrol if required
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
{
|
||||
if self.config.lock().unwrap().pvmemcontrol.is_some() {
|
||||
let (pvmemcontrol_bus_device, pvmemcontrol_pci_device) =
|
||||
self.make_pvmemcontrol_device()?;
|
||||
self.pvmemcontrol_devices =
|
||||
Some((pvmemcontrol_bus_device, pvmemcontrol_pci_device));
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.clone().lock().unwrap().pvpanic {
|
||||
self.pvpanic_device = self.add_pvpanic_device()?;
|
||||
}
|
||||
@@ -1400,11 +1460,11 @@ impl DeviceManager {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
if let Some(pci_config_io) = segment.pci_config_io.as_ref() {
|
||||
self.bus_devices
|
||||
.push(Arc::clone(pci_config_io) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(pci_config_io) as Arc<dyn BusDeviceSync>);
|
||||
}
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&segment.pci_config_mmio) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&segment.pci_config_mmio) as Arc<dyn BusDeviceSync>);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1489,7 +1549,7 @@ impl DeviceManager {
|
||||
.map_err(DeviceManagerError::BusError)?;
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&interrupt_controller) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&interrupt_controller) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
// Fill the device tree with a new node. In case of restore, we
|
||||
// know there is nothing to do, so we can simply override the
|
||||
@@ -1521,7 +1581,7 @@ impl DeviceManager {
|
||||
)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&shutdown_device) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&shutdown_device) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
@@ -1584,12 +1644,12 @@ impl DeviceManager {
|
||||
)
|
||||
.map_err(DeviceManagerError::BusError)?;
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&ged_device) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&ged_device) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
let pm_timer_device = Arc::new(Mutex::new(devices::AcpiPmTimerDevice::new()));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&pm_timer_device) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&pm_timer_device) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
@@ -1629,7 +1689,7 @@ impl DeviceManager {
|
||||
)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&i8042) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&i8042) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
self.address_manager
|
||||
.io_bus
|
||||
@@ -1657,7 +1717,7 @@ impl DeviceManager {
|
||||
)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&cmos) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&cmos) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
self.address_manager
|
||||
.io_bus
|
||||
@@ -1667,7 +1727,7 @@ impl DeviceManager {
|
||||
let fwdebug = Arc::new(Mutex::new(devices::legacy::FwDebugDevice::new()));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&fwdebug) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&fwdebug) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
self.address_manager
|
||||
.io_bus
|
||||
@@ -1678,7 +1738,7 @@ impl DeviceManager {
|
||||
// 0x80 debug port
|
||||
let debug_port = Arc::new(Mutex::new(devices::legacy::DebugPort::new(self.timestamp)));
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&debug_port) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&debug_port) as Arc<dyn BusDeviceSync>);
|
||||
self.address_manager
|
||||
.io_bus
|
||||
.insert(debug_port, 0x80, 0x1)
|
||||
@@ -1710,7 +1770,7 @@ impl DeviceManager {
|
||||
let rtc_device = Arc::new(Mutex::new(devices::legacy::Rtc::new(interrupt_group)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&rtc_device) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&rtc_device) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
let addr = arch::layout::LEGACY_RTC_MAPPED_IO_START;
|
||||
|
||||
@@ -1752,7 +1812,7 @@ impl DeviceManager {
|
||||
)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&gpio_device) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&gpio_device) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
let addr = arch::layout::LEGACY_GPIO_MAPPED_IO_START;
|
||||
|
||||
@@ -1802,7 +1862,7 @@ impl DeviceManager {
|
||||
.unwrap_or(debug_console::DEFAULT_PORT);
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&debug_console) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&debug_console) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
self.address_manager
|
||||
.allocator
|
||||
@@ -1853,7 +1913,7 @@ impl DeviceManager {
|
||||
)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&serial) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&serial) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
self.address_manager
|
||||
.allocator
|
||||
@@ -1910,7 +1970,7 @@ impl DeviceManager {
|
||||
)));
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&serial) as Arc<Mutex<dyn BusDevice>>);
|
||||
.push(Arc::clone(&serial) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
let addr = arch::layout::LEGACY_SERIAL_MAPPED_IO_START;
|
||||
|
||||
@@ -3048,6 +3108,48 @@ impl DeviceManager {
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
fn make_pvmemcontrol_device(
|
||||
&mut self,
|
||||
) -> DeviceManagerResult<(
|
||||
Arc<PvmemcontrolBusDevice>,
|
||||
Arc<Mutex<PvmemcontrolPciDevice>>,
|
||||
)> {
|
||||
let id = String::from(PVMEMCONTROL_DEVICE_NAME);
|
||||
let pci_segment_id = 0x0_u16;
|
||||
|
||||
let (pci_segment_id, pci_device_bdf, resources) =
|
||||
self.pci_resources(&id, pci_segment_id)?;
|
||||
|
||||
info!("Creating pvmemcontrol device: id = {}", id);
|
||||
let (pvmemcontrol_pci_device, pvmemcontrol_bus_device) =
|
||||
devices::pvmemcontrol::PvmemcontrolDevice::make_device(
|
||||
id.clone(),
|
||||
self.memory_manager.lock().unwrap().guest_memory(),
|
||||
);
|
||||
|
||||
let pvmemcontrol_pci_device = Arc::new(Mutex::new(pvmemcontrol_pci_device));
|
||||
let pvmemcontrol_bus_device = Arc::new(pvmemcontrol_bus_device);
|
||||
|
||||
let new_resources = self.add_pci_device(
|
||||
pvmemcontrol_bus_device.clone(),
|
||||
pvmemcontrol_pci_device.clone(),
|
||||
pci_segment_id,
|
||||
pci_device_bdf,
|
||||
resources,
|
||||
)?;
|
||||
|
||||
let mut node = device_node!(id, pvmemcontrol_pci_device);
|
||||
|
||||
node.resources = new_resources;
|
||||
node.pci_bdf = Some(pci_device_bdf);
|
||||
node.pci_device_handle = None;
|
||||
|
||||
self.device_tree.lock().unwrap().insert(id, node);
|
||||
|
||||
Ok((pvmemcontrol_bus_device, pvmemcontrol_pci_device))
|
||||
}
|
||||
|
||||
fn make_virtio_balloon_devices(&mut self) -> DeviceManagerResult<Vec<MetaVirtioDevice>> {
|
||||
let mut devices = Vec::new();
|
||||
|
||||
@@ -3412,7 +3514,7 @@ impl DeviceManager {
|
||||
|
||||
fn add_pci_device(
|
||||
&mut self,
|
||||
bus_device: Arc<Mutex<dyn BusDevice>>,
|
||||
bus_device: Arc<dyn BusDeviceSync>,
|
||||
pci_device: Arc<Mutex<dyn PciDevice>>,
|
||||
segment_id: u16,
|
||||
bdf: PciBdf,
|
||||
@@ -3639,15 +3741,22 @@ impl DeviceManager {
|
||||
// Create the AccessPlatform trait from the implementation IommuMapping.
|
||||
// This will provide address translation for any virtio device sitting
|
||||
// behind a vIOMMU.
|
||||
let access_platform: Option<Arc<dyn AccessPlatform>> = if let Some(mapping) = iommu_mapping
|
||||
{
|
||||
Some(Arc::new(AccessPlatformMapping::new(
|
||||
let mut access_platform: Option<Arc<dyn AccessPlatform>> = None;
|
||||
|
||||
if let Some(mapping) = iommu_mapping {
|
||||
access_platform = Some(Arc::new(AccessPlatformMapping::new(
|
||||
pci_device_bdf.into(),
|
||||
mapping.clone(),
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
)));
|
||||
}
|
||||
|
||||
// If SEV-SNP is enabled create the AccessPlatform from SevSnpPageAccessProxy
|
||||
#[cfg(feature = "sev_snp")]
|
||||
if self.config.lock().unwrap().is_sev_snp_enabled() {
|
||||
access_platform = Some(Arc::new(SevSnpPageAccessProxy::new(
|
||||
self.address_manager.vm.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
let memory = self.memory_manager.lock().unwrap().guest_memory();
|
||||
|
||||
@@ -4076,7 +4185,7 @@ impl DeviceManager {
|
||||
|
||||
(
|
||||
Arc::clone(&vfio_pci_device) as Arc<Mutex<dyn PciDevice>>,
|
||||
Arc::clone(&vfio_pci_device) as Arc<Mutex<dyn BusDevice>>,
|
||||
Arc::clone(&vfio_pci_device) as Arc<dyn BusDeviceSync>,
|
||||
None as Option<Arc<Mutex<dyn virtio_devices::VirtioDevice>>>,
|
||||
false,
|
||||
)
|
||||
@@ -4108,7 +4217,7 @@ impl DeviceManager {
|
||||
|
||||
(
|
||||
Arc::clone(&virtio_pci_device) as Arc<Mutex<dyn PciDevice>>,
|
||||
Arc::clone(&virtio_pci_device) as Arc<Mutex<dyn BusDevice>>,
|
||||
Arc::clone(&virtio_pci_device) as Arc<dyn BusDeviceSync>,
|
||||
Some(dev.virtio_device()),
|
||||
dev.dma_handler().is_some() && !iommu_attached,
|
||||
)
|
||||
@@ -4124,7 +4233,7 @@ impl DeviceManager {
|
||||
|
||||
(
|
||||
Arc::clone(&vfio_user_pci_device) as Arc<Mutex<dyn PciDevice>>,
|
||||
Arc::clone(&vfio_user_pci_device) as Arc<Mutex<dyn BusDevice>>,
|
||||
Arc::clone(&vfio_user_pci_device) as Arc<dyn BusDeviceSync>,
|
||||
None as Option<Arc<Mutex<dyn virtio_devices::VirtioDevice>>>,
|
||||
true,
|
||||
)
|
||||
|
||||
153
vmm/src/landlock.rs
Normal file
153
vmm/src/landlock.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright © 2024 Microsoft Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
use landlock::make_bitflags;
|
||||
use landlock::{
|
||||
path_beneath_rules, Access, AccessFs, BitFlags, Ruleset, RulesetAttr, RulesetCreated,
|
||||
RulesetCreatedAttr, RulesetError, ABI,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::io::Error as IoError;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LandlockError {
|
||||
/// All RulesetErrors from Landlock library are wrapped in this error
|
||||
#[error("Error creating/adding/restricting ruleset: {0}")]
|
||||
ManageRuleset(#[source] RulesetError),
|
||||
|
||||
/// Error opening path
|
||||
#[error("Error opening path: {0}")]
|
||||
OpenPath(#[source] IoError),
|
||||
|
||||
/// Invalid Landlock access
|
||||
#[error("Invalid Landlock access: {0}")]
|
||||
InvalidLandlockAccess(String),
|
||||
|
||||
/// Invalid Path
|
||||
#[error("Invalid path")]
|
||||
InvalidPath,
|
||||
}
|
||||
|
||||
// https://docs.rs/landlock/latest/landlock/enum.ABI.html for more info on ABI
|
||||
static ABI: ABI = ABI::V3;
|
||||
|
||||
pub(crate) struct LandlockAccess {
|
||||
access: BitFlags<AccessFs>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for LandlockAccess {
|
||||
type Error = LandlockError;
|
||||
|
||||
fn try_from(s: &str) -> Result<LandlockAccess, LandlockError> {
|
||||
if s.is_empty() {
|
||||
return Err(LandlockError::InvalidLandlockAccess(
|
||||
"Access cannot be empty".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut access = BitFlags::<AccessFs>::empty();
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'r' => access |= AccessFs::from_read(ABI),
|
||||
'w' => access |= AccessFs::from_write(ABI),
|
||||
_ => {
|
||||
return Err(LandlockError::InvalidLandlockAccess(
|
||||
format!("Invalid access: {c}").to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(LandlockAccess { access })
|
||||
}
|
||||
}
|
||||
pub struct Landlock {
|
||||
ruleset: RulesetCreated,
|
||||
}
|
||||
|
||||
impl Landlock {
|
||||
pub fn new() -> Result<Landlock, LandlockError> {
|
||||
let file_access = AccessFs::from_all(ABI);
|
||||
|
||||
let def_ruleset = Ruleset::default()
|
||||
.handle_access(file_access)
|
||||
.map_err(LandlockError::ManageRuleset)?;
|
||||
|
||||
// By default, rulesets are created in `BestEffort` mode. This lets Landlock
|
||||
// to enable all the supported rules and silently ignore the unsupported ones.
|
||||
let ruleset = def_ruleset.create().map_err(LandlockError::ManageRuleset)?;
|
||||
|
||||
Ok(Landlock { ruleset })
|
||||
}
|
||||
|
||||
pub(crate) fn add_rule(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
access: BitFlags<AccessFs>,
|
||||
) -> Result<(), LandlockError> {
|
||||
// path_beneath_rules in landlock crate handles file and directory access rules.
|
||||
// Incoming path/s are passed to path_beneath_rules, so that we don't
|
||||
// have to worry about the type of the path.
|
||||
let paths = vec![path.clone()];
|
||||
let path_beneath_rules = path_beneath_rules(paths, access);
|
||||
self.ruleset
|
||||
.as_mut()
|
||||
.add_rules(path_beneath_rules)
|
||||
.map_err(LandlockError::ManageRuleset)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn add_rule_with_access(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
access: &str,
|
||||
) -> Result<(), LandlockError> {
|
||||
self.add_rule(path, LandlockAccess::try_from(access)?.access)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restrict_self(self) -> Result<(), LandlockError> {
|
||||
self.ruleset
|
||||
.restrict_self()
|
||||
.map_err(LandlockError::ManageRuleset)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_access() {
|
||||
// These access rights could change in future versions of Landlock. Listing
|
||||
// them here explicitly to raise their visibility during code reviews.
|
||||
let read_access = make_bitflags!(AccessFs::{
|
||||
Execute
|
||||
| ReadFile
|
||||
| ReadDir
|
||||
});
|
||||
let write_access = make_bitflags!(AccessFs::{
|
||||
WriteFile
|
||||
| RemoveDir
|
||||
| RemoveFile
|
||||
| MakeChar
|
||||
| MakeDir
|
||||
| MakeReg
|
||||
| MakeSock
|
||||
| MakeFifo
|
||||
| MakeBlock
|
||||
| MakeSym
|
||||
| Refer
|
||||
| Truncate
|
||||
});
|
||||
let landlock_access = LandlockAccess::try_from("rw").unwrap();
|
||||
assert!(landlock_access.access == read_access | write_access);
|
||||
|
||||
let landlock_access = LandlockAccess::try_from("r").unwrap();
|
||||
assert!(landlock_access.access == read_access);
|
||||
|
||||
let landlock_access = LandlockAccess::try_from("w").unwrap();
|
||||
assert!(landlock_access.access == write_access);
|
||||
|
||||
assert!(LandlockAccess::try_from("").is_err());
|
||||
}
|
||||
175
vmm/src/lib.rs
175
vmm/src/lib.rs
@@ -18,6 +18,7 @@ use crate::config::{
|
||||
};
|
||||
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
||||
use crate::coredump::GuestDebuggable;
|
||||
use crate::landlock::Landlock;
|
||||
use crate::memory_manager::MemoryManager;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use crate::migration::get_vm_snapshot;
|
||||
@@ -29,6 +30,7 @@ use anyhow::anyhow;
|
||||
use api::dbus::{DBusApiOptions, DBusApiShutdownChannels};
|
||||
use api::http::HttpApiHandle;
|
||||
use console_devices::{pre_create_console_devices, ConsoleInfo};
|
||||
use landlock::LandlockError;
|
||||
use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW};
|
||||
use memory_manager::MemoryManagerSnapshotData;
|
||||
use pci::PciBdf;
|
||||
@@ -75,6 +77,7 @@ mod gdb;
|
||||
#[cfg(feature = "igvm")]
|
||||
mod igvm;
|
||||
pub mod interrupt;
|
||||
pub mod landlock;
|
||||
pub mod memory_manager;
|
||||
pub mod migration;
|
||||
mod pci_segment;
|
||||
@@ -195,6 +198,14 @@ pub enum Error {
|
||||
|
||||
#[error("Failed to join on threads: {0:?}")]
|
||||
ThreadCleanup(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
|
||||
|
||||
/// Cannot create Landlock object
|
||||
#[error("Error creating landlock object: {0}")]
|
||||
CreateLandlock(LandlockError),
|
||||
|
||||
/// Cannot apply landlock based sandboxing
|
||||
#[error("Error applying landlock: {0}")]
|
||||
ApplyLandlock(LandlockError),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -327,6 +338,7 @@ pub fn feature_list() -> Vec<String> {
|
||||
pub fn start_event_monitor_thread(
|
||||
mut monitor: event_monitor::Monitor,
|
||||
seccomp_action: &SeccompAction,
|
||||
landlock_enable: bool,
|
||||
hypervisor_type: hypervisor::HypervisorType,
|
||||
exit_event: EventFd,
|
||||
) -> Result<thread::JoinHandle<Result<()>>> {
|
||||
@@ -347,6 +359,17 @@ pub fn start_event_monitor_thread(
|
||||
e
|
||||
})?;
|
||||
}
|
||||
if landlock_enable {
|
||||
Landlock::new()
|
||||
.map_err(Error::CreateLandlock)?
|
||||
.restrict_self()
|
||||
.map_err(Error::ApplyLandlock)
|
||||
.map_err(|e| {
|
||||
error!("Error applying landlock to event monitor thread: {:?}", e);
|
||||
exit_event.write(1).ok();
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
std::panic::catch_unwind(AssertUnwindSafe(move || {
|
||||
while let Ok(event) = monitor.rx.recv() {
|
||||
@@ -389,6 +412,7 @@ pub fn start_vmm_thread(
|
||||
exit_event: EventFd,
|
||||
seccomp_action: &SeccompAction,
|
||||
hypervisor: Arc<dyn hypervisor::Hypervisor>,
|
||||
landlock_enable: bool,
|
||||
) -> Result<VmmThreadHandle> {
|
||||
#[cfg(feature = "guest_debug")]
|
||||
let gdb_hw_breakpoints = hypervisor.get_guest_debug_hw_bps();
|
||||
@@ -429,7 +453,7 @@ pub fn start_vmm_thread(
|
||||
exit_event,
|
||||
)?;
|
||||
|
||||
vmm.setup_signal_handler()?;
|
||||
vmm.setup_signal_handler(landlock_enable)?;
|
||||
|
||||
vmm.control_loop(
|
||||
Rc::new(api_receiver),
|
||||
@@ -466,6 +490,7 @@ pub fn start_vmm_thread(
|
||||
seccomp_action,
|
||||
exit_event,
|
||||
hypervisor_type,
|
||||
landlock_enable,
|
||||
)?)
|
||||
} else if let Some(http_fd) = http_fd {
|
||||
Some(api::start_http_fd_thread(
|
||||
@@ -475,6 +500,7 @@ pub fn start_vmm_thread(
|
||||
seccomp_action,
|
||||
exit_event,
|
||||
hypervisor_type,
|
||||
landlock_enable,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
@@ -590,7 +616,7 @@ impl Vmm {
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_signal_handler(&mut self) -> Result<()> {
|
||||
fn setup_signal_handler(&mut self, landlock_enable: bool) -> Result<()> {
|
||||
let signals = Signals::new(Self::HANDLED_SIGNALS);
|
||||
match signals {
|
||||
Ok(signals) => {
|
||||
@@ -617,6 +643,21 @@ impl Vmm {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if landlock_enable{
|
||||
match Landlock::new() {
|
||||
Ok(landlock) => {
|
||||
let _ = landlock.restrict_self().map_err(Error::ApplyLandlock).map_err(|e| {
|
||||
error!("Error applying Landlock to signal handler thread: {:?}", e);
|
||||
exit_evt.write(1).ok();
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error creating Landlock object: {:?}", e);
|
||||
exit_evt.write(1).ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
Vmm::signal_handler(signals, original_termios_opt, &exit_evt);
|
||||
}))
|
||||
@@ -634,6 +675,7 @@ impl Vmm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
vmm_version: VmmVersionInfo,
|
||||
api_evt: EventFd,
|
||||
@@ -724,6 +766,19 @@ impl Vmm {
|
||||
MigratableError::MigrateReceive(anyhow!("Error creating console devices: {:?}", e))
|
||||
})?);
|
||||
|
||||
if self
|
||||
.vm_config
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.landlock_enable
|
||||
{
|
||||
apply_landlock(self.vm_config.as_ref().unwrap().clone()).map_err(|e| {
|
||||
MigratableError::MigrateReceive(anyhow!("Error applying landlock: {:?}", e))
|
||||
})?;
|
||||
}
|
||||
|
||||
let vm = Vm::create_hypervisor_vm(
|
||||
&self.hypervisor,
|
||||
#[cfg(feature = "tdx")]
|
||||
@@ -848,9 +903,8 @@ impl Vmm {
|
||||
// And then read the memory itself
|
||||
memory_manager
|
||||
.receive_memory_regions(&table, socket)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
Response::error().write_to(socket).ok();
|
||||
e
|
||||
})?;
|
||||
Response::ok().write_to(socket)?;
|
||||
Ok(())
|
||||
@@ -884,15 +938,10 @@ impl Vmm {
|
||||
table.write_to(socket)?;
|
||||
// And then the memory itself
|
||||
vm.send_memory_regions(&table, socket)?;
|
||||
let res = Response::read_from(socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error during dirty memory migration");
|
||||
Request::abandon().write_to(socket)?;
|
||||
Response::read_from(socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error during dirty memory migration"
|
||||
)));
|
||||
}
|
||||
Response::read_from(socket)?.ok_or_abandon(
|
||||
socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")),
|
||||
)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -911,15 +960,10 @@ impl Vmm {
|
||||
|
||||
// Start the migration
|
||||
Request::start().write_to(&mut socket)?;
|
||||
let res = Response::read_from(&mut socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error starting migration");
|
||||
Request::abandon().write_to(&mut socket)?;
|
||||
Response::read_from(&mut socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error starting migration"
|
||||
)));
|
||||
}
|
||||
Response::read_from(&mut socket)?.ok_or_abandon(
|
||||
&mut socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error starting migration")),
|
||||
)?;
|
||||
|
||||
// Send config
|
||||
let vm_config = vm.get_config();
|
||||
@@ -966,15 +1010,10 @@ impl Vmm {
|
||||
socket
|
||||
.write_all(&config_data)
|
||||
.map_err(MigratableError::MigrateSocket)?;
|
||||
let res = Response::read_from(&mut socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error during config migration");
|
||||
Request::abandon().write_to(&mut socket)?;
|
||||
Response::read_from(&mut socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error during config migration"
|
||||
)));
|
||||
}
|
||||
Response::read_from(&mut socket)?.ok_or_abandon(
|
||||
&mut socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error during config migration")),
|
||||
)?;
|
||||
|
||||
// Let every Migratable object know about the migration being started.
|
||||
vm.start_migration()?;
|
||||
@@ -994,15 +1033,10 @@ impl Vmm {
|
||||
table.write_to(&mut socket)?;
|
||||
// And then the memory itself
|
||||
vm.send_memory_regions(&table, &mut socket)?;
|
||||
let res = Response::read_from(&mut socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error during memory migration");
|
||||
Request::abandon().write_to(&mut socket)?;
|
||||
Response::read_from(&mut socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error during memory migration"
|
||||
)));
|
||||
}
|
||||
Response::read_from(&mut socket)?.ok_or_abandon(
|
||||
&mut socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error during dirty memory migration")),
|
||||
)?;
|
||||
|
||||
// Try at most 5 passes of dirty memory sending
|
||||
const MAX_DIRTY_MIGRATIONS: usize = 5;
|
||||
@@ -1029,27 +1063,17 @@ impl Vmm {
|
||||
socket
|
||||
.write_all(&snapshot_data)
|
||||
.map_err(MigratableError::MigrateSocket)?;
|
||||
let res = Response::read_from(&mut socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error during state migration");
|
||||
Request::abandon().write_to(&mut socket)?;
|
||||
Response::read_from(&mut socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error during state migration"
|
||||
)));
|
||||
}
|
||||
|
||||
Response::read_from(&mut socket)?.ok_or_abandon(
|
||||
&mut socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error during state migration")),
|
||||
)?;
|
||||
// Complete the migration
|
||||
Request::complete().write_to(&mut socket)?;
|
||||
let res = Response::read_from(&mut socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error completing migration");
|
||||
Request::abandon().write_to(&mut socket)?;
|
||||
Response::read_from(&mut socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error completing migration"
|
||||
)));
|
||||
}
|
||||
Response::read_from(&mut socket)?.ok_or_abandon(
|
||||
&mut socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error completing migration")),
|
||||
)?;
|
||||
|
||||
info!("Migration complete");
|
||||
|
||||
// Let every Migratable object know about the migration being complete
|
||||
@@ -1209,6 +1233,11 @@ impl Vmm {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_landlock(vm_config: Arc<Mutex<VmConfig>>) -> result::Result<(), LandlockError> {
|
||||
vm_config.lock().unwrap().apply_landlock()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl RequestHandler for Vmm {
|
||||
fn vm_create(&mut self, config: Arc<Mutex<VmConfig>>) -> result::Result<(), VmError> {
|
||||
// We only store the passed VM config.
|
||||
@@ -1217,6 +1246,18 @@ impl RequestHandler for Vmm {
|
||||
self.vm_config = Some(config);
|
||||
self.console_info =
|
||||
Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?);
|
||||
|
||||
if self
|
||||
.vm_config
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.landlock_enable
|
||||
{
|
||||
apply_landlock(self.vm_config.as_ref().unwrap().clone())
|
||||
.map_err(VmError::ApplyLandlock)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(VmError::VmAlreadyCreated)
|
||||
@@ -1400,6 +1441,18 @@ impl RequestHandler for Vmm {
|
||||
)?;
|
||||
self.vm = Some(vm);
|
||||
|
||||
if self
|
||||
.vm_config
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.landlock_enable
|
||||
{
|
||||
apply_landlock(self.vm_config.as_ref().unwrap().clone())
|
||||
.map_err(VmError::ApplyLandlock)?;
|
||||
}
|
||||
|
||||
// Now we can restore the rest of the VM.
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.restore()
|
||||
@@ -2177,6 +2230,8 @@ mod unit_tests {
|
||||
user_devices: None,
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
pvpanic: false,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -2189,6 +2244,8 @@ mod unit_tests {
|
||||
platform: None,
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use pci::{PciConfigIo, PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use uuid::Uuid;
|
||||
use vm_allocator::AddressAllocator;
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::BusDeviceSync;
|
||||
|
||||
pub(crate) struct PciSegment {
|
||||
pub(crate) id: u16,
|
||||
@@ -70,7 +70,7 @@ impl PciSegment {
|
||||
address_manager
|
||||
.mmio_bus
|
||||
.insert(
|
||||
Arc::clone(&pci_config_mmio) as Arc<Mutex<dyn BusDevice>>,
|
||||
Arc::clone(&pci_config_mmio) as Arc<dyn BusDeviceSync>,
|
||||
mmio_config_address,
|
||||
layout::PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
)
|
||||
|
||||
@@ -153,106 +153,79 @@ use kvm::*;
|
||||
|
||||
// MSHV IOCTL code. This is unstable until the kernel code has been declared stable.
|
||||
#[cfg(feature = "mshv")]
|
||||
mod mshv {
|
||||
pub const MSHV_GET_API_VERSION: u64 = 0xb800;
|
||||
pub const MSHV_CREATE_VM: u64 = 0x4028_b801;
|
||||
pub const MSHV_MAP_GUEST_MEMORY: u64 = 0x4020_b802;
|
||||
pub const MSHV_UNMAP_GUEST_MEMORY: u64 = 0x4020_b803;
|
||||
pub const MSHV_CREATE_VP: u64 = 0x4004_b804;
|
||||
pub const MSHV_IRQFD: u64 = 0x4010_b80e;
|
||||
pub const MSHV_IOEVENTFD: u64 = 0x4020_b80f;
|
||||
pub const MSHV_SET_MSI_ROUTING: u64 = 0x4008_b811;
|
||||
pub const MSHV_GET_VP_REGISTERS: u64 = 0xc010_b805;
|
||||
pub const MSHV_SET_VP_REGISTERS: u64 = 0x4010_b806;
|
||||
pub const MSHV_RUN_VP: u64 = 0x8100_b807;
|
||||
pub const MSHV_GET_VP_STATE: u64 = 0xc010_b80a;
|
||||
pub const MSHV_SET_VP_STATE: u64 = 0xc010_b80b;
|
||||
pub const MSHV_SET_PARTITION_PROPERTY: u64 = 0x4010_b80c;
|
||||
pub const MSHV_GET_PARTITION_PROPERTY: u64 = 0xc010_b80d;
|
||||
pub const MSHV_GET_GPA_ACCESS_STATES: u64 = 0xc01c_b812;
|
||||
pub const MSHV_VP_TRANSLATE_GVA: u64 = 0xc020_b80e;
|
||||
pub const MSHV_CREATE_PARTITION: u64 = 0x4030_b801;
|
||||
pub const MSHV_CREATE_DEVICE: u64 = 0xc00c_b813;
|
||||
pub const MSHV_SET_DEVICE_ATTR: u64 = 0x4018_b814;
|
||||
pub const MSHV_VP_REGISTER_INTERCEPT_RESULT: u64 = 0x4030_b817;
|
||||
pub const MSHV_GET_VP_CPUID_VALUES: u64 = 0xc028_b81b;
|
||||
pub const MSHV_MODIFY_GPA_HOST_ACCESS: u64 = 0x4018_b828;
|
||||
pub const MSHV_IMPORT_ISOLATED_PAGES: u64 = 0x4010_b829;
|
||||
pub const MSHV_COMPLETE_ISOLATED_IMPORT: u64 = 0x4d06_b830;
|
||||
pub const MSHV_READ_GPA: u64 = 0xc020_b832;
|
||||
pub const MSHV_WRITE_GPA: u64 = 0x4020_b833;
|
||||
pub const MSHV_SEV_SNP_AP_CREATE: u64 = 0x4010_b834;
|
||||
pub const MSHV_ISSUE_PSP_GUEST_REQUEST: u64 = 0x4010_b831;
|
||||
pub const MSHV_ASSERT_INTERRUPT: u64 = 0x4018_b809;
|
||||
pub const MSHV_ROOT_HVCALL: u64 = 0xc020_b835;
|
||||
}
|
||||
#[cfg(feature = "mshv")]
|
||||
use mshv::*;
|
||||
use hypervisor::mshv::mshv_ioctls::*;
|
||||
|
||||
#[cfg(feature = "mshv")]
|
||||
fn create_vmm_ioctl_seccomp_rule_common_mshv() -> Result<Vec<SeccompRule>, BackendError> {
|
||||
Ok(or![
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_API_VERSION,)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_VM)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_MAP_GUEST_MEMORY)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_UNMAP_GUEST_MEMORY)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_VP)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IRQFD)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IOEVENTFD)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_MSI_ROUTING)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_REGISTERS)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_VP_REGISTERS)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_RUN_VP)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_ASSERT_INTERRUPT)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_STATE)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_VP_STATE)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_PARTITION())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_MAP_GUEST_MEMORY())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_UNMAP_GUEST_MEMORY())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_VP())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IRQFD())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IOEVENTFD())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_MSI_ROUTING())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_REGISTERS())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_VP_REGISTERS())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_RUN_VP())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_STATE())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_VP_STATE())?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_SET_PARTITION_PROPERTY
|
||||
MSHV_SET_PARTITION_PROPERTY()
|
||||
)?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_GET_PARTITION_PROPERTY
|
||||
MSHV_GET_PARTITION_PROPERTY()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_GPA_ACCESS_STATES)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_VP_TRANSLATE_GVA)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_PARTITION)?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_VP_REGISTER_INTERCEPT_RESULT
|
||||
MSHV_GET_GPA_ACCESS_STATES()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_DEVICE)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_DEVICE_ATTR)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_CPUID_VALUES)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_VP_TRANSLATE_GVA())?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_MODIFY_GPA_HOST_ACCESS
|
||||
MSHV_VP_REGISTER_INTERCEPT_RESULT()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IMPORT_ISOLATED_PAGES)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_CREATE_DEVICE())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_DEVICE_ATTR())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_CPUID_VALUES())?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_COMPLETE_ISOLATED_IMPORT
|
||||
MSHV_MODIFY_GPA_HOST_ACCESS()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_READ_GPA)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_WRITE_GPA)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SEV_SNP_AP_CREATE)?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_ISSUE_PSP_GUEST_REQUEST
|
||||
MSHV_IMPORT_ISOLATED_PAGES()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_ROOT_HVCALL)?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_COMPLETE_ISOLATED_IMPORT()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_READ_GPA())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_WRITE_GPA())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SEV_SNP_AP_CREATE())?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_ISSUE_PSP_GUEST_REQUEST()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_ROOT_HVCALL())?],
|
||||
])
|
||||
}
|
||||
|
||||
@@ -502,9 +475,12 @@ fn signal_handler_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, Backend
|
||||
(libc::SYS_exit_group, vec![]),
|
||||
(libc::SYS_futex, vec![]),
|
||||
(libc::SYS_ioctl, create_signal_handler_ioctl_seccomp_rule()?),
|
||||
(libc::SYS_landlock_create_ruleset, vec![]),
|
||||
(libc::SYS_landlock_restrict_self, vec![]),
|
||||
(libc::SYS_madvise, vec![]),
|
||||
(libc::SYS_mmap, vec![]),
|
||||
(libc::SYS_munmap, vec![]),
|
||||
(libc::SYS_prctl, vec![]),
|
||||
(libc::SYS_recvfrom, vec![]),
|
||||
(libc::SYS_rt_sigprocmask, vec![]),
|
||||
(libc::SYS_rt_sigreturn, vec![]),
|
||||
@@ -607,6 +583,9 @@ fn vmm_thread_rules(
|
||||
(libc::SYS_io_uring_setup, vec![]),
|
||||
(libc::SYS_io_uring_register, vec![]),
|
||||
(libc::SYS_kill, vec![]),
|
||||
(libc::SYS_landlock_create_ruleset, vec![]),
|
||||
(libc::SYS_landlock_add_rule, vec![]),
|
||||
(libc::SYS_landlock_restrict_self, vec![]),
|
||||
(libc::SYS_listen, vec![]),
|
||||
(libc::SYS_lseek, vec![]),
|
||||
(libc::SYS_madvise, vec![]),
|
||||
@@ -708,33 +687,32 @@ fn create_vcpu_ioctl_seccomp_rule_kvm() -> Result<Vec<SeccompRule>, BackendError
|
||||
#[cfg(feature = "mshv")]
|
||||
fn create_vcpu_ioctl_seccomp_rule_mshv() -> Result<Vec<SeccompRule>, BackendError> {
|
||||
Ok(or![
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_MSI_ROUTING)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IOEVENTFD)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IRQFD)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_RUN_VP)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_REGISTERS)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_VP_REGISTERS)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_MAP_GUEST_MEMORY)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_UNMAP_GUEST_MEMORY)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_ASSERT_INTERRUPT)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_VP_TRANSLATE_GVA)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_CPUID_VALUES)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_MSI_ROUTING())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IOEVENTFD())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_IRQFD())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_RUN_VP())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_REGISTERS())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SET_VP_REGISTERS())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_MAP_GUEST_MEMORY())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_UNMAP_GUEST_MEMORY())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_VP_TRANSLATE_GVA())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_GET_VP_CPUID_VALUES())?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_MODIFY_GPA_HOST_ACCESS
|
||||
MSHV_MODIFY_GPA_HOST_ACCESS()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_READ_GPA)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_WRITE_GPA)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SEV_SNP_AP_CREATE)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_READ_GPA())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_WRITE_GPA())?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_SEV_SNP_AP_CREATE())?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::Dword,
|
||||
Eq,
|
||||
MSHV_ISSUE_PSP_GUEST_REQUEST
|
||||
MSHV_ISSUE_PSP_GUEST_REQUEST()
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_ROOT_HVCALL)?],
|
||||
and![Cond::new(1, ArgLen::Dword, Eq, MSHV_ROOT_HVCALL())?],
|
||||
])
|
||||
}
|
||||
|
||||
@@ -845,10 +823,13 @@ fn http_api_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError>
|
||||
(libc::SYS_futex, vec![]),
|
||||
(libc::SYS_getrandom, vec![]),
|
||||
(libc::SYS_ioctl, create_api_ioctl_seccomp_rule()?),
|
||||
(libc::SYS_landlock_create_ruleset, vec![]),
|
||||
(libc::SYS_landlock_restrict_self, vec![]),
|
||||
(libc::SYS_madvise, vec![]),
|
||||
(libc::SYS_mmap, vec![]),
|
||||
(libc::SYS_mprotect, vec![]),
|
||||
(libc::SYS_munmap, vec![]),
|
||||
(libc::SYS_prctl, vec![]),
|
||||
(libc::SYS_recvfrom, vec![]),
|
||||
(libc::SYS_recvmsg, vec![]),
|
||||
(libc::SYS_sched_yield, vec![]),
|
||||
@@ -898,9 +879,13 @@ fn dbus_api_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError>
|
||||
fn event_monitor_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError> {
|
||||
Ok(vec![
|
||||
(libc::SYS_brk, vec![]),
|
||||
(libc::SYS_close, vec![]),
|
||||
(libc::SYS_futex, vec![]),
|
||||
(libc::SYS_landlock_create_ruleset, vec![]),
|
||||
(libc::SYS_landlock_restrict_self, vec![]),
|
||||
(libc::SYS_mmap, vec![]),
|
||||
(libc::SYS_munmap, vec![]),
|
||||
(libc::SYS_prctl, vec![]),
|
||||
(libc::SYS_sched_yield, vec![]),
|
||||
(libc::SYS_write, vec![]),
|
||||
])
|
||||
|
||||
@@ -28,6 +28,7 @@ use crate::device_tree::DeviceTree;
|
||||
use crate::gdb::{Debuggable, DebuggableError, GdbRequestPayload, GdbResponsePayload};
|
||||
#[cfg(feature = "igvm")]
|
||||
use crate::igvm::igvm_loader;
|
||||
use crate::landlock::LandlockError;
|
||||
use crate::memory_manager::{
|
||||
Error as MemoryManagerError, MemoryManager, MemoryManagerSnapshotData,
|
||||
};
|
||||
@@ -92,7 +93,7 @@ use vm_memory::{Address, ByteValued, GuestMemoryRegion, ReadVolatile};
|
||||
use vm_memory::{
|
||||
Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, WriteVolatile,
|
||||
};
|
||||
use vm_migration::protocol::{Request, Response, Status};
|
||||
use vm_migration::protocol::{Request, Response};
|
||||
use vm_migration::{
|
||||
protocol::MemoryRangeTable, snapshot_from_id, Migratable, MigratableError, Pausable, Snapshot,
|
||||
Snapshottable, Transportable,
|
||||
@@ -122,6 +123,9 @@ pub enum Error {
|
||||
#[error("Cannot load the kernel command line in memory: {0}")]
|
||||
LoadCmdLine(#[source] linux_loader::loader::Error),
|
||||
|
||||
#[error("Failed to apply landlock config during vm_create: {0}")]
|
||||
ApplyLandlock(#[source] LandlockError),
|
||||
|
||||
#[error("Cannot modify the kernel command line: {0}")]
|
||||
CmdLineInsertStr(#[source] linux_loader::cmdline::Error),
|
||||
|
||||
@@ -2237,15 +2241,10 @@ impl Vm {
|
||||
MigratableError::MigrateSend(anyhow!("Error sending memory fd: {}", e))
|
||||
})?;
|
||||
|
||||
let res = Response::read_from(socket)?;
|
||||
if res.status() != Status::Ok {
|
||||
warn!("Error during memory fd migration");
|
||||
Request::abandon().write_to(socket)?;
|
||||
Response::read_from(socket).ok();
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Error during memory fd migration"
|
||||
)));
|
||||
}
|
||||
Response::read_from(socket)?.ok_or_abandon(
|
||||
socket,
|
||||
MigratableError::MigrateSend(anyhow!("Error during memory fd migration")),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2,11 +2,20 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
use crate::{landlock::LandlockError, Landlock};
|
||||
use net_util::MacAddr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{net::Ipv4Addr, path::PathBuf};
|
||||
use std::{fs, net::Ipv4Addr, path::PathBuf, result};
|
||||
use virtio_devices::RateLimiterConfig;
|
||||
|
||||
pub type LandlockResult<T> = result::Result<T, LandlockError>;
|
||||
|
||||
/// Trait to apply Landlock on VmConfig elements
|
||||
pub(crate) trait ApplyLandlock {
|
||||
/// Apply Landlock rules to file paths
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct CpuAffinity {
|
||||
pub vcpu: u8,
|
||||
@@ -132,6 +141,15 @@ pub struct MemoryZoneConfig {
|
||||
pub prefault: bool,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for MemoryZoneConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
if let Some(file) = &self.file {
|
||||
landlock.add_rule_with_access(file.to_path_buf(), "rw")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
|
||||
pub enum HotplugMethod {
|
||||
#[default]
|
||||
@@ -245,6 +263,15 @@ pub struct DiskConfig {
|
||||
pub queue_affinity: Option<Vec<VirtQueueAffinity>>,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for DiskConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
if let Some(path) = &self.path {
|
||||
landlock.add_rule_with_access(path.to_path_buf(), "rw")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_DISK_NUM_QUEUES: usize = 1;
|
||||
|
||||
pub fn default_diskconfig_num_queues() -> usize {
|
||||
@@ -378,6 +405,14 @@ impl Default for RngConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplyLandlock for RngConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
// Rng Path only need read access
|
||||
landlock.add_rule_with_access(self.src.to_path_buf(), "r")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct BalloonConfig {
|
||||
pub size: u64,
|
||||
@@ -389,6 +424,10 @@ pub struct BalloonConfig {
|
||||
pub free_page_reporting: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
|
||||
pub struct PvmemcontrolConfig {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct FsConfig {
|
||||
pub tag: String,
|
||||
@@ -411,6 +450,13 @@ pub fn default_fsconfig_queue_size() -> u16 {
|
||||
1024
|
||||
}
|
||||
|
||||
impl ApplyLandlock for FsConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct PmemConfig {
|
||||
pub file: PathBuf,
|
||||
@@ -426,6 +472,13 @@ pub struct PmemConfig {
|
||||
pub pci_segment: u16,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for PmemConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.file.to_path_buf(), "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum ConsoleOutputMode {
|
||||
Off,
|
||||
@@ -450,6 +503,18 @@ pub fn default_consoleconfig_file() -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
impl ApplyLandlock for ConsoleConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
if let Some(file) = &self.file {
|
||||
landlock.add_rule_with_access(file.to_path_buf(), "rw")?;
|
||||
}
|
||||
if let Some(socket) = &self.socket {
|
||||
landlock.add_rule_with_access(socket.to_path_buf(), "rw")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct DebugConsoleConfig {
|
||||
@@ -470,6 +535,15 @@ impl Default for DebugConsoleConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl ApplyLandlock for DebugConsoleConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
if let Some(file) = &self.file {
|
||||
landlock.add_rule_with_access(file.to_path_buf(), "rw")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct DeviceConfig {
|
||||
@@ -484,6 +558,22 @@ pub struct DeviceConfig {
|
||||
pub x_nv_gpudirect_clique: Option<u8>,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for DeviceConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
let device_path = fs::read_link(self.path.as_path()).map_err(LandlockError::OpenPath)?;
|
||||
let iommu_group = device_path.file_name();
|
||||
let iommu_group_str = iommu_group
|
||||
.ok_or(LandlockError::InvalidPath)?
|
||||
.to_str()
|
||||
.ok_or(LandlockError::InvalidPath)?;
|
||||
|
||||
let vfio_group_path = "/dev/vfio/".to_owned() + iommu_group_str;
|
||||
landlock.add_rule_with_access(vfio_group_path.into(), "rw")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct UserDeviceConfig {
|
||||
pub socket: PathBuf,
|
||||
@@ -493,6 +583,13 @@ pub struct UserDeviceConfig {
|
||||
pub pci_segment: u16,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for UserDeviceConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct VdpaConfig {
|
||||
pub path: PathBuf,
|
||||
@@ -510,6 +607,13 @@ pub fn default_vdpaconfig_num_queues() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
impl ApplyLandlock for VdpaConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.path.to_path_buf(), "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct VsockConfig {
|
||||
pub cid: u32,
|
||||
@@ -522,6 +626,13 @@ pub struct VsockConfig {
|
||||
pub pci_segment: u16,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for VsockConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct SgxEpcConfig {
|
||||
@@ -575,6 +686,30 @@ pub struct PayloadConfig {
|
||||
pub host_data: Option<String>,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for PayloadConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
// Payload only needs read access
|
||||
if let Some(firmware) = &self.firmware {
|
||||
landlock.add_rule_with_access(firmware.to_path_buf(), "r")?;
|
||||
}
|
||||
|
||||
if let Some(kernel) = &self.kernel {
|
||||
landlock.add_rule_with_access(kernel.to_path_buf(), "r")?;
|
||||
}
|
||||
|
||||
if let Some(initramfs) = &self.initramfs {
|
||||
landlock.add_rule_with_access(initramfs.to_path_buf(), "r")?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "igvm")]
|
||||
if let Some(igvm) = &self.igvm {
|
||||
landlock.add_rule_with_access(igvm.to_path_buf(), "r")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_serial() -> ConsoleConfig {
|
||||
ConsoleConfig {
|
||||
file: None,
|
||||
@@ -598,6 +733,26 @@ pub struct TpmConfig {
|
||||
pub socket: PathBuf,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for TpmConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct LandlockConfig {
|
||||
pub path: PathBuf,
|
||||
pub access: String,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for LandlockConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(self.path.to_path_buf(), self.access.clone().as_str())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct VmConfig {
|
||||
#[serde(default)]
|
||||
@@ -624,6 +779,9 @@ pub struct VmConfig {
|
||||
pub user_devices: Option<Vec<UserDeviceConfig>>,
|
||||
pub vdpa: Option<Vec<VdpaConfig>>,
|
||||
pub vsock: Option<VsockConfig>,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
#[serde(default)]
|
||||
pub pvmemcontrol: Option<PvmemcontrolConfig>,
|
||||
#[serde(default)]
|
||||
pub pvpanic: bool,
|
||||
#[serde(default)]
|
||||
@@ -645,4 +803,94 @@ pub struct VmConfig {
|
||||
// valid, and will be closed when the holding VmConfig instance is destroyed.
|
||||
#[serde(skip)]
|
||||
pub preserved_fds: Option<Vec<i32>>,
|
||||
#[serde(default)]
|
||||
pub landlock_enable: bool,
|
||||
pub landlock_rules: Option<Vec<LandlockConfig>>,
|
||||
}
|
||||
|
||||
impl VmConfig {
|
||||
pub(crate) fn apply_landlock(&self) -> LandlockResult<()> {
|
||||
let mut landlock = Landlock::new()?;
|
||||
|
||||
if let Some(mem_zones) = &self.memory.zones {
|
||||
for zone in mem_zones.iter() {
|
||||
zone.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
let disks = &self.disks;
|
||||
if let Some(disks) = disks {
|
||||
for disk in disks.iter() {
|
||||
disk.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.rng.apply_landlock(&mut landlock)?;
|
||||
|
||||
if let Some(fs_configs) = &self.fs {
|
||||
for fs_config in fs_configs.iter() {
|
||||
fs_config.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pmem_configs) = &self.pmem {
|
||||
for pmem_config in pmem_configs.iter() {
|
||||
pmem_config.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.console.apply_landlock(&mut landlock)?;
|
||||
self.serial.apply_landlock(&mut landlock)?;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
self.debug_console.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
|
||||
if let Some(devices) = &self.devices {
|
||||
landlock.add_rule_with_access("/dev/vfio/vfio".into(), "rw")?;
|
||||
|
||||
for device in devices.iter() {
|
||||
device.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(user_devices) = &self.user_devices {
|
||||
for user_devices in user_devices.iter() {
|
||||
user_devices.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vdpa_configs) = &self.vdpa {
|
||||
for vdpa_config in vdpa_configs.iter() {
|
||||
vdpa_config.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vsock_config) = &self.vsock {
|
||||
vsock_config.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
|
||||
if let Some(payload) = &self.payload {
|
||||
payload.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
|
||||
if let Some(tpm_config) = &self.tpm {
|
||||
tpm_config.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
|
||||
if self.net.is_some() {
|
||||
landlock.add_rule_with_access("/dev/net/tun".into(), "rw")?;
|
||||
}
|
||||
|
||||
if let Some(landlock_rules) = &self.landlock_rules {
|
||||
for landlock_rule in landlock_rules.iter() {
|
||||
landlock_rule.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
landlock.restrict_self()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user