diff --git a/Cargo.lock b/Cargo.lock index ada18428cc6..a9d6d0b1180 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -72,6 +72,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75" +dependencies = [ + "bitflags", + "cexpr", + "cfg-if 0.1.10", + "clang-sys", + "lazy_static", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -102,6 +121,22 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + [[package]] name = "cast" version = "0.2.3" @@ -111,6 +146,21 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cexpr" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -123,6 +173,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.33.3" @@ -134,6 +195,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "const_fn" version = "0.4.4" @@ -198,7 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils", + "crossbeam-utils 0.8.1", ] [[package]] @@ -209,7 +279,7 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils", + "crossbeam-utils 0.8.1", ] [[package]] @@ -220,12 +290,23 @@ checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" dependencies = [ "cfg-if 1.0.0", "const_fn", - "crossbeam-utils", + "crossbeam-utils 0.8.1", "lazy_static", "memoffset", "scopeguard", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + [[package]] name = "crossbeam-utils" version = "0.8.1" @@ -318,6 +399,52 @@ dependencies = [ "vmm", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "futures-core" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "half" version = "1.6.0" @@ -333,6 +460,15 @@ dependencies = [ "libc", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + [[package]] name = "itertools" version = "0.9.0" @@ -374,6 +510,16 @@ dependencies = [ "vm-memory 0.1.0", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kvm-bindings" version = "0.4.0" @@ -407,6 +553,25 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi 0.3.9", +] + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.11" @@ -428,6 +593,12 @@ dependencies = [ "utils", ] +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memchr" version = "2.3.4" @@ -452,6 +623,48 @@ dependencies = [ "utils", ] +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + [[package]] name = "mmds" version = "0.1.0" @@ -467,10 +680,44 @@ dependencies = [ "versionize_derive", ] +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + [[package]] name = "net_gen" version = "0.1.0" +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -496,6 +743,59 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core", + "rustc_version", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "passfd" +version = "0.1.4" +dependencies = [ + "futures", + "futures-core", + "libc", + "mio", + "mio-uds", + "tempdir", + "tokio", + "tokio-reactor", + "tokio-uds", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + [[package]] name = "plotters" version = "0.2.15" @@ -534,6 +834,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rate_limiter" version = "0.1.0" @@ -567,11 +895,26 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils", + "crossbeam-utils 0.8.1", "lazy_static", "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + [[package]] name = "regex" version = "1.4.2" @@ -599,6 +942,21 @@ version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -692,6 +1050,27 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + [[package]] name = "snapshot" version = "0.1.0" @@ -713,6 +1092,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -722,6 +1111,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -750,6 +1159,99 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "lazy_static", + "libc", + "mio", + "mio-uds", + "pin-project-lite", +] + +[[package]] +name = "tokio-codec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" +dependencies = [ + "bytes 0.4.12", + "futures", + "tokio-io", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes 0.4.12", + "futures", + "log", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures", + "lazy_static", + "log", + "mio", + "num_cpus", + "parking_lot", + "slab", + "tokio-executor", + "tokio-io", + "tokio-sync", +] + +[[package]] +name = "tokio-sync" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +dependencies = [ + "fnv", + "futures", +] + +[[package]] +name = "tokio-uds" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" +dependencies = [ + "bytes 0.4.12", + "futures", + "iovec", + "libc", + "log", + "mio", + "mio-uds", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + [[package]] name = "unicode-width" version = "0.1.8" @@ -762,6 +1264,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "userfaultfd" +version = "0.2.1-dev" +dependencies = [ + "bitflags", + "libc", + "logger", + "nix", + "thiserror", + "userfaultfd-sys", +] + +[[package]] +name = "userfaultfd-sys" +version = "0.2.1-dev" +dependencies = [ + "bindgen", + "cc", + "cfg-if 0.1.10", +] + [[package]] name = "utils" version = "0.1.0" @@ -773,6 +1296,12 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "versionize" version = "0.1.6" @@ -821,7 +1350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45b5b0a6f371f8147143b1adb95edddafc9cb9e40adaf94edb6f93a1d04b0330" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -839,12 +1368,14 @@ dependencies = [ "libc", "logger", "mmds", + "passfd", "polly", "rate_limiter", "seccomp", "serde", "serde_json", "snapshot", + "userfaultfd", "utils", "versionize", "versionize_derive", @@ -861,6 +1392,12 @@ dependencies = [ "libc", ] +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "walkdir" version = "2.3.1" @@ -868,7 +1405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", - "winapi", + "winapi 0.3.9", "winapi-util", ] @@ -936,6 +1473,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -946,6 +1489,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -958,7 +1507,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -966,3 +1515,13 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/src/passfd/Cargo.toml b/src/passfd/Cargo.toml new file mode 100644 index 00000000000..4f910257dd6 --- /dev/null +++ b/src/passfd/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "passfd" +version = "0.1.4" +authors = ["Alexander Polakov "] +edition = "2018" +description = "File descriptor passing" +license = "MIT" +documentation = "https://docs.rs/passfd/" +repository = "https://github.com/polachok/passfd" +homepage = "https://github.com/polachok/passfd" + +[dependencies] +libc = "0.2" +tokio-uds = { version = "0.2", optional = true } +tokio-reactor = { version = "0.1", optional = true } +futures = { version = "0.1", optional = true } +mio = { version = "0.6", optional = true } +mio-uds = { version = "0.6", optional = true } +tokio2 = { package = "tokio", version = "0.2", features = ["uds","io-driver"], optional = true } +futures-core = { version = "0.3", optional = true } + +[dev-dependencies] +tempdir = "0.3" + +[features] +tokio_01 = ["tokio-uds", "tokio-reactor", "futures", "mio", "mio-uds"] +tokio_02 = ["tokio2", "mio", "mio-uds", "futures-core"] diff --git a/src/passfd/README.md b/src/passfd/README.md new file mode 100644 index 00000000000..2b5d2f5b978 --- /dev/null +++ b/src/passfd/README.md @@ -0,0 +1,11 @@ +# passfd + +[![Build Status](https://travis-ci.com/polachok/passfd.svg?branch=master)](https://travis-ci.com/polachok/passfd) + +Unix sockets possess magic ability to transfer file descriptors from one process to another (unrelated) process using +obscure `SCM_RIGHTS` API. This little crate adds extension methods to [UnixStream](https://doc.rust-lang.org/std/os/unix/net/struct.UnixStream.html) to use it. + +## Links + +* [fd-passing](https://docs.rs/fd-passing/0.0.1/fd_passing/) same thing, different API +* [Good article on fd passing](https://keithp.com/blogs/fd-passing/) diff --git a/src/passfd/examples/receive.rs b/src/passfd/examples/receive.rs new file mode 100644 index 00000000000..3b319a90ac0 --- /dev/null +++ b/src/passfd/examples/receive.rs @@ -0,0 +1,15 @@ +use passfd::FdPassingExt; +use std::fs::File; +use std::io::Read; +use std::os::unix::io::FromRawFd; +use std::os::unix::net::UnixStream; + +fn main() { + let stream = UnixStream::connect("/tmp/test.sock").unwrap(); + let fd = stream.recv_fd().unwrap(); + let mut file = unsafe { File::from_raw_fd(fd) }; + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + println!("{}", buf); + std::thread::sleep(std::time::Duration::from_secs(30)); +} diff --git a/src/passfd/examples/send.rs b/src/passfd/examples/send.rs new file mode 100644 index 00000000000..8d39139a5d1 --- /dev/null +++ b/src/passfd/examples/send.rs @@ -0,0 +1,11 @@ +use passfd::FdPassingExt; +use std::fs::File; +use std::os::unix::io::AsRawFd; +use std::os::unix::net::UnixListener; + +fn main() { + let file = File::open("/etc/passwd").unwrap(); + let listener = UnixListener::bind("/tmp/test.sock").unwrap(); + let (stream, _) = listener.accept().unwrap(); + stream.send_fd(file.as_raw_fd()).unwrap(); +} diff --git a/src/passfd/src/lib.rs b/src/passfd/src/lib.rs new file mode 100644 index 00000000000..c99a08285d0 --- /dev/null +++ b/src/passfd/src/lib.rs @@ -0,0 +1,272 @@ +//! `passfd` allows passing file descriptors between unrelated processes +//! using Unix sockets. +//! +//! Both tokio 0.1 and 0.2 are supported with `tokio_01` and `tokio_02` +//! features. Please note that these features rely on internal representation +//! of UnixStream and are unsafe. +//! +//! # Example usage +//! ## Process 1 (sender) +//! ``` +//! use passfd::FdPassingExt; +//! use std::fs::File; +//! use std::os::unix::io::AsRawFd; +//! use std::os::unix::net::UnixListener; +//! +//! let file = File::open("/etc/passwd").unwrap(); +//! let listener = UnixListener::bind("/tmp/test.sock").unwrap(); +//! let (stream, _) = listener.accept().unwrap(); +//! stream.send_fd(file.as_raw_fd()).unwrap(); +//! ``` +//! ## Process 2 (receiver) +//! ``` +//! use passfd::FdPassingExt; +//! use std::fs::File; +//! use std::io::Read; +//! use std::os::unix::io::FromRawFd; +//! use std::os::unix::net::UnixStream; +//! +//! let stream = UnixStream::connect("/tmp/test.sock").unwrap(); +//! let fd = stream.recv_fd().unwrap(); +//! let mut file = unsafe { File::from_raw_fd(fd) }; +//! let mut buf = String::new(); +//! file.read_to_string(&mut buf).unwrap(); +//! println!("{}", buf); +//! ``` + +use libc::{self, c_int, c_void, msghdr}; +use std::io::{Error, ErrorKind}; +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::net::UnixStream; + +#[cfg(feature = "tokio_01")] +pub mod tokio_01; + +// Support for tokio 0.2 +#[cfg(feature = "tokio_02")] +pub mod tokio_02; + +/// Main trait, extends UnixStream +pub trait FdPassingExt { + /// Send RawFd. No type information is transmitted. + fn send_fd(&self, fd: RawFd) -> Result<(), Error>; + /// Receive RawFd. No type information is transmitted. + fn recv_fd(&self) -> Result; +} + +impl FdPassingExt for UnixStream { + fn send_fd(&self, fd: RawFd) -> Result<(), Error> { + self.as_raw_fd().send_fd(fd) + } + + fn recv_fd(&self) -> Result { + self.as_raw_fd().recv_fd() + } +} + +// buffer must be aligned to header (See cmsg(3)) +#[repr(C)] +union HeaderAlignedBuf { + // CMSG_SPACE(mem::size_of::()) = 24 (linux x86_64), + // we leave some space just in case + // TODO: use CMSPG_SPACE when it's const fn + // https://github.com/rust-lang/rust/issues/64926 + buf: [libc::c_char; 256], + align: libc::cmsghdr, +} + +impl FdPassingExt for RawFd { + fn send_fd(&self, fd: RawFd) -> Result<(), Error> { + let mut dummy: c_int = 0; + let msg_len = unsafe { libc::CMSG_SPACE(mem::size_of::() as u32) as _ }; + let mut u = HeaderAlignedBuf { buf: [0; 256] }; + let mut iov = libc::iovec { + iov_base: &mut dummy as *mut c_int as *mut c_void, + iov_len: mem::size_of_val(&dummy), + }; + + #[cfg(target_env = "gnu")] + let msg: msghdr = libc::msghdr { + msg_name: std::ptr::null_mut(), + msg_namelen: 0, + msg_iov: &mut iov, + msg_iovlen: 1, + msg_control: unsafe { u.buf.as_mut_ptr() as *mut c_void }, + msg_controllen: msg_len, + msg_flags: 0, + }; + #[cfg(target_env = "musl")] + let msg = unsafe { + // Musl's msghdr has private fields, so this is the only way to + // initialize it. + let mut msghdr = mem::MaybeUninit::::zeroed(); + let p = msghdr.as_mut_ptr(); + (*p).msg_name = std::ptr::null_mut(); + (*p).msg_namelen = 0; + (*p).msg_iov = &mut iov; + (*p).msg_iovlen = 1; + (*p).msg_control = u.buf.as_mut_ptr() as *mut c_void; + (*p).msg_controllen = msg_len; + (*p).msg_flags = 0; + msghdr.assume_init() + }; + + unsafe { + let hdr = libc::cmsghdr { + #[cfg(target_env = "musl")] + __pad1: 0, + cmsg_level: libc::SOL_SOCKET, + cmsg_type: libc::SCM_RIGHTS, + cmsg_len: libc::CMSG_LEN(mem::size_of::() as u32) as _, + }; + // https://github.com/rust-lang/rust-clippy/issues/2881 + #[allow(clippy::cast_ptr_alignment)] + std::ptr::write_unaligned(libc::CMSG_FIRSTHDR(&msg), hdr); + + // https://github.com/rust-lang/rust-clippy/issues/2881 + #[allow(clippy::cast_ptr_alignment)] + std::ptr::write_unaligned( + libc::CMSG_DATA(u.buf.as_mut_ptr() as *const _) as *mut c_int, + fd, + ); + } + + let rv = unsafe { libc::sendmsg(*self, &msg, 0) }; + if rv < 0 { + return Err(Error::last_os_error()); + } + + Ok(()) + } + + fn recv_fd(&self) -> Result { + let mut dummy: c_int = -1; + let msg_len = unsafe { libc::CMSG_SPACE(mem::size_of::() as u32) as _ }; + let mut u = HeaderAlignedBuf { buf: [0; 256] }; + let mut iov = libc::iovec { + iov_base: &mut dummy as *mut c_int as *mut c_void, + iov_len: mem::size_of_val(&dummy), + }; + #[cfg(target_env = "gnu")] + let mut msg: msghdr = libc::msghdr { + msg_name: std::ptr::null_mut(), + msg_namelen: 0, + msg_iov: &mut iov, + msg_iovlen: 1, + msg_control: unsafe { u.buf.as_mut_ptr() as *mut c_void }, + msg_controllen: msg_len, + msg_flags: 0, + }; + #[cfg(target_env = "musl")] + let mut msg = unsafe { + // Musl's msghdr has private fields, so this is the only way to + // initialize it. + let mut msghdr = mem::MaybeUninit::::zeroed(); + let p = msghdr.as_mut_ptr(); + (*p).msg_name = std::ptr::null_mut(); + (*p).msg_namelen = 0; + (*p).msg_iov = &mut iov; + (*p).msg_iovlen = 1; + (*p).msg_control = u.buf.as_mut_ptr() as *mut c_void; + (*p).msg_controllen = msg_len; + (*p).msg_flags = 0; + msghdr.assume_init() + }; + + unsafe { + let rv = libc::recvmsg(*self, &mut msg, 0); + match rv { + 0 => Err(Error::new(ErrorKind::UnexpectedEof, "0 bytes read")), + rv if rv < 0 => Err(Error::last_os_error()), + rv if rv == mem::size_of::() as isize => { + let hdr: *mut libc::cmsghdr = + if msg.msg_controllen >= mem::size_of::() as _ { + msg.msg_control as *mut libc::cmsghdr + } else { + return Err(Error::new( + ErrorKind::InvalidData, + "bad control msg (header)", + )); + }; + if (*hdr).cmsg_level != libc::SOL_SOCKET || (*hdr).cmsg_type != libc::SCM_RIGHTS + { + return Err(Error::new( + ErrorKind::InvalidData, + "bad control msg (level)", + )); + } + if msg.msg_controllen != libc::CMSG_SPACE(mem::size_of::() as u32) as _ { + return Err(Error::new(ErrorKind::InvalidData, "bad control msg (len)")); + } + // https://github.com/rust-lang/rust-clippy/issues/2881 + #[allow(clippy::cast_ptr_alignment)] + let fd = std::ptr::read_unaligned(libc::CMSG_DATA(hdr) as *mut c_int); + if libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) < 0 { + return Err(Error::last_os_error()); + } + Ok(fd) + } + _ => Err(Error::new( + ErrorKind::InvalidData, + "bad control msg (ret code)", + )), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::Read; + use std::os::unix::io::{AsRawFd, FromRawFd}; + use std::os::unix::net::{UnixListener, UnixStream}; + use tempdir::TempDir; + + #[test] + fn assert_sized() { + let msg_len = unsafe { libc::CMSG_SPACE(mem::size_of::() as u32) as usize }; + let u = HeaderAlignedBuf { buf: [0; 256] }; + assert!(msg_len < std::mem::size_of_val(&u)); + } + + #[test] + fn it_works() { + let tmp_dir = TempDir::new("passfd").unwrap(); + let sock_path = tmp_dir.path().join("listener.sock"); + + match unsafe { libc::fork() } { + -1 => panic!("fork went wrong"), + 0 => { + println!("child process, wait for socket to appear"); + for _ in 0..10 { + match UnixStream::connect(sock_path.clone()) { + Ok(stream) => { + println!("stream connected"); + let fd = stream.recv_fd().unwrap(); + let mut file = unsafe { File::from_raw_fd(fd) }; + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + return; + } + Err(_) => { + println!("not connected"); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + } + } + } + _ => { + println!("parent, start listening"); + let listener = UnixListener::bind(sock_path).unwrap(); + println!("opening file"); + let file = File::open("/etc/passwd").unwrap(); + let (stream, _) = listener.accept().unwrap(); + println!("client connected, sending fd"); + stream.send_fd(file.as_raw_fd()).unwrap(); + } + } + } +} diff --git a/src/passfd/src/tokio_01.rs b/src/passfd/src/tokio_01.rs new file mode 100644 index 00000000000..06c47549737 --- /dev/null +++ b/src/passfd/src/tokio_01.rs @@ -0,0 +1,59 @@ +//! Support for tokio 0.1 UnixStream. +//! It does a really bad `mem::transmute`, which is *NOT SAFE* + +use std::io::{Error, ErrorKind}; +use std::os::unix::io::{AsRawFd, RawFd}; + +use futures::Async; +use tokio_uds::UnixStream; + +use crate::FdPassingExt as SyncFdPassingExt; +use mio::Ready; + +/// Main trait, extends UnixStream +pub trait FdPassingExt { + /// Send RawFd. No type information is transmitted. + fn poll_send_fd(&self, fd: RawFd) -> Result, Error>; + /// Receive RawFd. No type information is transmitted. + fn poll_recv_fd(&self) -> Result, Error>; +} + +impl FdPassingExt for UnixStream { + fn poll_send_fd(&self, fd: RawFd) -> Result, Error> { + self.poll_write_ready()?; + + match self.as_raw_fd().send_fd(fd) { + Ok(_) => Ok(Async::Ready(())), + Err(err) if err.kind() == ErrorKind::WouldBlock => { + unsafe { clear_write_ready(self)? }; + Ok(Async::NotReady) + } + Err(err) => Err(err), + } + } + + fn poll_recv_fd(&self) -> Result, Error> { + self.poll_read_ready(Ready::readable())?; + + match self.as_raw_fd().recv_fd() { + Ok(val) => Ok(Async::Ready(val)), + Err(err) if err.kind() == ErrorKind::WouldBlock => { + unsafe { clear_read_ready(self, Ready::readable())? }; + Ok(Async::NotReady) + } + Err(err) => Err(err), + } + } +} + +unsafe fn clear_read_ready(stream: &UnixStream, ready: Ready) -> Result<(), Error> { + use tokio_reactor::PollEvented; + let inner: &PollEvented = std::mem::transmute(stream); + inner.clear_read_ready(ready) +} + +unsafe fn clear_write_ready(stream: &UnixStream) -> Result<(), Error> { + use tokio_reactor::PollEvented; + let inner: &PollEvented = std::mem::transmute(stream); + inner.clear_write_ready() +} diff --git a/src/passfd/src/tokio_02.rs b/src/passfd/src/tokio_02.rs new file mode 100644 index 00000000000..f319003920a --- /dev/null +++ b/src/passfd/src/tokio_02.rs @@ -0,0 +1,90 @@ +//! Support for tokio 0.2 UnixStream. +//! It does a really bad `mem::transmute`, which is *NOT SAFE* + +use std::future::Future; +use std::io::{Error, ErrorKind}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; + +use futures_core::ready; + +use tokio2::io::PollEvented; +use tokio2::net::UnixStream; + +use crate::FdPassingExt as SyncFdPassingExt; +use mio::Ready; + +/// Main trait, extends UnixStream +pub trait FdPassingExt { + /// Send RawFd. No type information is transmitted. + fn send_fd(&self, fd: RawFd) -> SendFd; + /// Receive RawFd. No type information is transmitted. + fn recv_fd(&self) -> RecvFd; +} + +pub struct SendFd<'a> { + stream: &'a UnixStream, + fd: RawFd, +} + +impl<'a> Future for SendFd<'a> { + type Output = Result<(), Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = Pin::into_inner(self); + let stream_fd = this.stream.as_raw_fd(); + let stream = unsafe { as_poll_evented(this.stream) }; + + ready!(stream.poll_write_ready(cx))?; + + match stream_fd.send_fd(this.fd) { + Ok(_) => Poll::Ready(Ok(())), + Err(err) if err.kind() == ErrorKind::WouldBlock => { + stream.clear_write_ready(cx)?; + Poll::Pending + } + Err(err) => Poll::Ready(Err(err)), + } + } +} + +pub struct RecvFd<'a> { + stream: &'a UnixStream, +} + +impl<'a> Future for RecvFd<'a> { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = Pin::into_inner(self); + let stream_fd = this.stream.as_raw_fd(); + let stream = unsafe { as_poll_evented(this.stream) }; + + ready!(stream.poll_read_ready(cx, Ready::readable()))?; + + match stream_fd.recv_fd() { + Ok(val) => Poll::Ready(Ok(val)), + Err(err) if err.kind() == ErrorKind::WouldBlock => { + stream.clear_read_ready(cx, Ready::readable())?; + Poll::Pending + } + Err(err) => Poll::Ready(Err(err)), + } + } +} + +impl FdPassingExt for UnixStream { + fn send_fd(&self, fd: RawFd) -> SendFd { + SendFd { stream: self, fd } + } + + fn recv_fd(&self) -> RecvFd { + RecvFd { stream: self } + } +} + +unsafe fn as_poll_evented(stream: &UnixStream) -> &PollEvented { + &*(stream as *const UnixStream as *const PollEvented) +} diff --git a/src/userfaultfd/CHANGELOG.md b/src/userfaultfd/CHANGELOG.md new file mode 100755 index 00000000000..3083ba7a7c9 --- /dev/null +++ b/src/userfaultfd/CHANGELOG.md @@ -0,0 +1,21 @@ +### Unreleased + +### 0.2.0 (2020-04-10) + +- Removed the compile-time Linux version check, and replaced it with a Cargo feature. + + The Linux version check was overly restrictive, even on systems that did have the right kernel + version installed but had older headers in `/usr/include/linux`. Beyond that, this check made it + more difficult to compile on a different host than what's targeted. + + There is now a `linux4_14` feature flag on `userfaultfd-sys`, which turns on and tests the extra + constants available in that version. Since `userfaultfd` did not make use of any of those newer + features, it doesn't have a feature flag yet. + + Applications should take care when initializing with `UffdBuilder` to specify the features and + ioctls they require, so that an unsupported version will be detected at runtime. + + +### 0.1.0 (2020-04-07) + +- Initial public release of userfaultfd-rs. diff --git a/src/userfaultfd/Cargo.toml b/src/userfaultfd/Cargo.toml new file mode 100644 index 00000000000..5d0635d185e --- /dev/null +++ b/src/userfaultfd/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "userfaultfd" +version = "0.2.1-dev" +authors = ["Adam C. Foltzer "] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "Rust bindings for the Linux userfaultfd functionality" +repository = "https://github.com/fastly/userfaultfd-rs" +readme = "README.md" + +[dependencies] +bitflags = "1.0" +libc = "0.2.65" +nix = "0.17" +thiserror = "1.0.4" +userfaultfd-sys = { path = "userfaultfd-sys", version = "0.2.1-dev" } + +logger = { path = "../logger" } \ No newline at end of file diff --git a/src/userfaultfd/LICENSE-APACHE b/src/userfaultfd/LICENSE-APACHE new file mode 100644 index 00000000000..1b5ec8b78e2 --- /dev/null +++ b/src/userfaultfd/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/src/userfaultfd/LICENSE-MIT b/src/userfaultfd/LICENSE-MIT new file mode 100644 index 00000000000..31aa79387f2 --- /dev/null +++ b/src/userfaultfd/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/userfaultfd/README.md b/src/userfaultfd/README.md new file mode 100644 index 00000000000..d4857431a87 --- /dev/null +++ b/src/userfaultfd/README.md @@ -0,0 +1,11 @@ +![Build](https://github.com/fastly/userfaultfd-rs/workflows/Rust/badge.svg) + +# Userfaultfd-rs +Rust bindings for Linux's userfaultfd functionality. + +## License + +This software is distributed under the terms of both the MIT license and the Apache License (Version 2.0). + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT). + diff --git a/src/userfaultfd/SECURITY.md b/src/userfaultfd/SECURITY.md new file mode 100644 index 00000000000..555ec878221 --- /dev/null +++ b/src/userfaultfd/SECURITY.md @@ -0,0 +1,9 @@ +## Report a security issue + +Fastly welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [Fastly’s security issue reporting process](https://www.fastly.com/security/report-security-issue). + +## Security advisories + +Remediation of security vulnerabilities is prioritized. The project team endeavors to coordinate remediation with third-party stakeholders, and is committed to transparency in the disclosure process. The team announces security issues via release notes as well as the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`) on a best-effort basis. + +Note that communications related to security issues in Fastly-maintained OSS as described here are distinct from [Fastly Security Advisories](https://www.fastly.com/security-advisories). diff --git a/src/userfaultfd/examples/manpage.rs b/src/userfaultfd/examples/manpage.rs new file mode 100644 index 00000000000..1a3206b9bdf --- /dev/null +++ b/src/userfaultfd/examples/manpage.rs @@ -0,0 +1,135 @@ +//! Port of the example from the `userfaultfd` manpage. +use libc::{self, c_void}; +use nix::poll::{poll, PollFd, PollFlags}; +use nix::sys::mman::{mmap, MapFlags, ProtFlags}; +use nix::unistd::{sysconf, SysconfVar}; +use std::env; +use std::os::unix::io::AsRawFd; +use std::ptr; +use userfaultfd::{Event, Uffd, UffdBuilder}; + +fn fault_handler_thread(uffd: Uffd) { + let page_size = sysconf(SysconfVar::PAGE_SIZE).unwrap().unwrap() as usize; + + // Create a page that will be copied into the faulting region + + let page = unsafe { + mmap( + ptr::null_mut(), + page_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, + -1, + 0, + ) + .expect("mmap") + }; + + // Loop, handling incoming events on the userfaultfd file descriptor + + let mut fault_cnt = 0; + loop { + // See what poll() tells us about the userfaultfd + + let pollfd = PollFd::new(uffd.as_raw_fd(), PollFlags::POLLIN); + let nready = poll(&mut [pollfd], -1).expect("poll"); + + println!("\nfault_handler_thread():"); + let revents = pollfd.revents().unwrap(); + println!( + " poll() returns: nready = {}; POLLIN = {}; POLLERR = {}", + nready, + revents.contains(PollFlags::POLLIN), + revents.contains(PollFlags::POLLERR), + ); + + // Read an event from the userfaultfd + let event = uffd + .read_event() + .expect("read uffd_msg") + .expect("uffd_msg ready"); + + // We expect only one kind of event; verify that assumption + + if let Event::Pagefault { addr, .. } = event { + // Display info about the page-fault event + + println!(" UFFD_EVENT_PAGEFAULT event: {:?}", event); + + // Copy the page pointed to by 'page' into the faulting region. Vary the contents that are + // copied in, so that it is more obvious that each fault is handled separately. + + for c in unsafe { std::slice::from_raw_parts_mut(page as *mut u8, page_size) } { + *c = b'A' + fault_cnt % 20; + } + fault_cnt += 1; + + let dst = (addr as usize & !(page_size as usize - 1)) as *mut c_void; + let copy = unsafe { uffd.copy(page, dst, page_size, true).expect("uffd copy") }; + + println!(" (uffdio_copy.copy returned {})", copy); + } else { + panic!("Unexpected event on userfaultfd"); + } + } +} + +fn main() { + let num_pages = env::args() + .nth(1) + .expect("Usage: manpage ") + .parse::() + .unwrap(); + + let page_size = sysconf(SysconfVar::PAGE_SIZE).unwrap().unwrap() as usize; + let len = num_pages * page_size; + + // Create and enable userfaultfd object + + let uffd = UffdBuilder::new() + .close_on_exec(true) + .non_blocking(true) + .create() + .expect("uffd creation"); + + // Create a private anonymous mapping. The memory will be demand-zero paged--that is, not yet + // allocated. When we actually touch the memory, it will be allocated via the userfaultfd. + + let addr = unsafe { + mmap( + ptr::null_mut(), + len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, + -1, + 0, + ) + .expect("mmap") + }; + + println!("Address returned by mmap() = {:p}", addr); + + // Register the memory range of the mapping we just created for handling by the userfaultfd + // object. In mode, we request to track missing pages (i.e., pages that have not yet been + // faulted in). + + uffd.register(addr, len).expect("uffd.register()"); + + // Create a thread that will process the userfaultfd events + let _s = std::thread::spawn(move || fault_handler_thread(uffd)); + + // Main thread now touches memory in the mapping, touching locations 1024 bytes apart. This will + // trigger userfaultfd events for all pages in the region. + + // Ensure that faulting address is not on a page boundary, in order to test that we correctly + // handle that case in fault_handling_thread() + let mut l = 0xf; + + while l < len { + let ptr = (addr as usize + l) as *mut u8; + let c = unsafe { *ptr }; + println!("Read address {:p} in main(): {:?}", ptr, c as char); + l += 1024; + std::thread::sleep(std::time::Duration::from_micros(100000)); + } +} diff --git a/src/userfaultfd/linux-version/Cargo.toml b/src/userfaultfd/linux-version/Cargo.toml new file mode 100644 index 00000000000..821657eea7e --- /dev/null +++ b/src/userfaultfd/linux-version/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "linux-version" +version = "0.1.2-dev" +authors = ["Adam C. Foltzer "] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "Package for detecting the version of the linux kernel a program is being run on." + +build = "build.rs" + +[dependencies] +nix = "0.17" +semver = "0.9" + +[build-dependencies] +bindgen = { version = "0.51", default-features = false } \ No newline at end of file diff --git a/src/userfaultfd/linux-version/build.rs b/src/userfaultfd/linux-version/build.rs new file mode 100644 index 00000000000..aa93b7ceebd --- /dev/null +++ b/src/userfaultfd/linux-version/build.rs @@ -0,0 +1,17 @@ +use bindgen; +use std::env; +use std::path::PathBuf; + +fn main() { + let bindings = bindgen::Builder::default() + .header("wrapper.h") + .whitelist_var("LINUX_VERSION_CODE") + .generate() + .expect("binding generation failed"); + println!("rerun-if-changed=/usr/include/linux/version.h"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("binding file couldn't be written"); +} diff --git a/src/userfaultfd/linux-version/src/lib.rs b/src/userfaultfd/linux-version/src/lib.rs new file mode 100644 index 00000000000..4f3bb163383 --- /dev/null +++ b/src/userfaultfd/linux-version/src/lib.rs @@ -0,0 +1,27 @@ +pub use semver::{SemVerError, Version}; + +mod raw { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + +/// Get the version specified in ``. +pub fn linux_headers_version() -> Version { + let version = raw::LINUX_VERSION_CODE as u64; + let major = version >> 16; + let minor = version >> 8 & 0xFF; + let patch = version & 0xFF; + Version::new(major, minor, patch) +} + +/// Get the version specified by `uname -r`. +/// +/// This treats everything after the `major.minor.patch` triple as build metadata. +pub fn linux_kernel_version() -> Result { + let uname = nix::sys::utsname::uname(); + let pre_ver = Version::parse(uname.release())?; + Ok(Version { + pre: vec![], + build: pre_ver.pre.clone(), + ..pre_ver + }) +} diff --git a/src/userfaultfd/linux-version/wrapper.h b/src/userfaultfd/linux-version/wrapper.h new file mode 100644 index 00000000000..e08d26ab78a --- /dev/null +++ b/src/userfaultfd/linux-version/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/src/userfaultfd/src/builder.rs b/src/userfaultfd/src/builder.rs new file mode 100644 index 00000000000..6cb755ea314 --- /dev/null +++ b/src/userfaultfd/src/builder.rs @@ -0,0 +1,111 @@ +use crate::error::{Error, Result}; +use crate::raw; +use crate::{IoctlFlags, Uffd}; +use bitflags::bitflags; +use nix::errno::Errno; + +bitflags! { + /// Used with `UffdBuilder` to determine which features are available in the current kernel. + pub struct FeatureFlags: u64 { + const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; + const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; + const EVENT_REMAP = raw::UFFD_FEATURE_EVENT_REMAP; + const EVENT_REMOVE = raw::UFFD_FEATURE_EVENT_REMOVE; + const MISSING_HUGETLBFS = raw::UFFD_FEATURE_MISSING_HUGETLBFS; + const MISSING_SHMEM = raw::UFFD_FEATURE_MISSING_SHMEM; + const EVENT_UNMAP = raw::UFFD_FEATURE_EVENT_UNMAP; + } +} + +/// A builder for initializing `Uffd` objects. +/// +/// ``` +/// use userfaultfd::UffdBuilder; +/// +/// let uffd = UffdBuilder::new() +/// .close_on_exec(true) +/// .non_blocking(true) +/// .create(); +/// assert!(uffd.is_ok()); +/// ``` +pub struct UffdBuilder { + close_on_exec: bool, + non_blocking: bool, + req_features: FeatureFlags, + req_ioctls: IoctlFlags, +} + +impl UffdBuilder { + /// Create a new builder with no required features or ioctls, and `close_on_exec` and + /// `non_blocking` both set to `false`. + pub fn new() -> UffdBuilder { + UffdBuilder { + close_on_exec: false, + non_blocking: false, + req_features: FeatureFlags::empty(), + req_ioctls: IoctlFlags::empty(), + } + } + + /// Enable the close-on-exec flag for the new userfaultfd object (see the description of + /// `O_CLOEXEC` in [`open(2)`](http://man7.org/linux/man-pages/man2/open.2.html)). + pub fn close_on_exec(&mut self, close_on_exec: bool) -> &mut Self { + self.close_on_exec = close_on_exec; + self + } + + /// Enable non-blocking operation for the userfaultfd object. + /// + /// If this is set to `false`, `Uffd::read_event()` will block until an event is available to + /// read. Otherwise, it will immediately return `None` if no event is available. + pub fn non_blocking(&mut self, non_blocking: bool) -> &mut Self { + self.non_blocking = non_blocking; + self + } + + /// Add a requirement that a particular feature or set of features is available. + /// + /// If a required feature is unavailable, `UffdBuilder.create()` will return an error. + pub fn require_features(&mut self, feature: FeatureFlags) -> &mut Self { + self.req_features |= feature; + self + } + + /// Add a requirement that a particular ioctl or set of ioctls is available. + /// + /// If a required ioctl is unavailable, `UffdBuilder.create()` will return an error. + pub fn require_ioctls(&mut self, ioctls: IoctlFlags) -> &mut Self { + self.req_ioctls |= ioctls; + self + } + + /// Create a `Uffd` object with the current settings of this builder. + pub fn create(&self) -> Result { + // first do the syscall to get the file descriptor + let mut flags = 0; + if self.close_on_exec { + flags |= libc::O_CLOEXEC; + } + if self.non_blocking { + flags |= libc::O_NONBLOCK; + } + let fd = Errno::result(unsafe { raw::userfaultfd(flags) })?; + + // then do the UFFDIO_API ioctl to set up and ensure features and other ioctls are available + let mut api = raw::uffdio_api { + api: raw::UFFD_API, + features: self.req_features.bits(), + ioctls: 0, + }; + unsafe { + raw::api(fd, &mut api as *mut raw::uffdio_api)?; + } + let supported = + IoctlFlags::from_bits(api.ioctls).ok_or(Error::UnrecognizedIoctls(api.ioctls))?; + if !supported.contains(self.req_ioctls) { + Err(Error::UnsupportedIoctls(supported)) + } else { + Ok(Uffd { fd }) + } + } +} diff --git a/src/userfaultfd/src/error.rs b/src/userfaultfd/src/error.rs new file mode 100644 index 00000000000..9ab6602ad88 --- /dev/null +++ b/src/userfaultfd/src/error.rs @@ -0,0 +1,52 @@ +use crate::IoctlFlags; +use thiserror::Error; +use nix::errno::Errno; + +pub type Result = std::result::Result; + +/// Errors for this crate. +/// +/// Several of these errors contain an underlying `Errno` value; see +/// [`userfaultfd(2)`](http://man7.org/linux/man-pages/man2/userfaultfd.2.html) and +/// [`ioctl_userfaultfd(2)`](http://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html) for more +/// details on how to interpret these errors. +#[derive(Debug, Error)] +pub enum Error { + /// Copy ioctl failure with `errno` value. + #[error("Copy failed")] + CopyFailed(Errno), + + /// Failure to read a full `uffd_msg` struct from the underlying file descriptor. + #[error("Incomplete uffd_msg; read only {read}/{expected} bytes")] + IncompleteMsg { read: usize, expected: usize }, + + /// Generic system error. + #[error("System error")] + SystemError(#[source] nix::Error), + + /// End-of-file was read from the underlying file descriptor. + #[error("EOF when reading file descriptor")] + ReadEof, + + /// An unrecognized event code was found in a `uffd_msg` struct. + #[error("Unrecognized event in uffd_msg: {0}")] + UnrecognizedEvent(u8), + + /// An unrecognized ioctl bit was set in the result of API initialization or registration. + #[error("Unrecognized ioctl flags: {0}")] + UnrecognizedIoctls(u64), + + /// Requested ioctls were not available when initializing the API. + #[error("Requested ioctls unsupported; supported: {0:?}")] + UnsupportedIoctls(IoctlFlags), + + /// Zeropage ioctl failure with `errno` value. + #[error("Zeropage failed: {0}")] + ZeropageFailed(Errno), +} + +impl From for Error { + fn from(e: nix::Error) -> Error { + Error::SystemError(e) + } +} diff --git a/src/userfaultfd/src/event.rs b/src/userfaultfd/src/event.rs new file mode 100644 index 00000000000..7f6d2b3d723 --- /dev/null +++ b/src/userfaultfd/src/event.rs @@ -0,0 +1,102 @@ +use crate::error::{Error, Result}; +use crate::raw; +use crate::Uffd; +use libc::c_void; +use std::os::unix::io::{FromRawFd, RawFd}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ReadWrite { + Read, + Write, +} + +/// Events from the userfaultfd object that are read by `Uffd::read_event()`. +#[derive(Debug)] +pub enum Event { + /// A pagefault event. + Pagefault { + /// Whether the fault is on a read or a write. + rw: ReadWrite, + /// The address that triggered the fault. + addr: *mut c_void, + }, + /// Generated when the faulting process invokes `fork(2)` (or `clone(2)` without the `CLONE_VM` + /// flag). + Fork { + /// The `Uffd` object created for the child by `fork(2)` + uffd: Uffd, + }, + /// Generated when the faulting process invokes `mremap(2)`. + Remap { + /// The original address of the memory range that was remapped. + from: *mut c_void, + /// The new address of the memory range that was remapped. + to: *mut c_void, + /// The original length of the memory range that was remapped. + len: usize, + }, + /// Generated when the faulting process invokes `madvise(2)` with `MADV_DONTNEED` or + /// `MADV_REMOVE` advice. + Remove { + /// The start address of the memory range that was freed. + start: *mut c_void, + /// The end address of the memory range that was freed. + end: *mut c_void, + }, + /// Generated when the faulting process unmaps a meomry range, either explicitly using + /// `munmap(2)` or implicitly during `mmap(2)` or `mremap(2)`. + Unmap { + /// The start address of the memory range that was unmapped. + start: *mut c_void, + /// The end address of the memory range that was unmapped. + end: *mut c_void, + }, +} + +impl Event { + pub(crate) fn from_uffd_msg(msg: &raw::uffd_msg) -> Result { + match msg.event { + raw::UFFD_EVENT_PAGEFAULT => { + let pagefault = unsafe { msg.arg.pagefault }; + let rw = if pagefault.flags & raw::UFFD_PAGEFAULT_FLAG_WRITE == 0 { + ReadWrite::Read + } else { + ReadWrite::Write + }; + Ok(Event::Pagefault { + rw, + addr: pagefault.address as *mut c_void, + }) + } + raw::UFFD_EVENT_FORK => { + let fork = unsafe { msg.arg.fork }; + Ok(Event::Fork { + uffd: unsafe { Uffd::from_raw_fd(fork.ufd as RawFd) }, + }) + } + raw::UFFD_EVENT_REMAP => { + let remap = unsafe { msg.arg.remap }; + Ok(Event::Remap { + from: remap.from as *mut c_void, + to: remap.to as *mut c_void, + len: remap.len as usize, + }) + } + raw::UFFD_EVENT_REMOVE => { + let remove = unsafe { msg.arg.remove }; + Ok(Event::Remove { + start: remove.start as *mut c_void, + end: remove.end as *mut c_void, + }) + } + raw::UFFD_EVENT_UNMAP => { + let remove = unsafe { msg.arg.remove }; + Ok(Event::Unmap { + start: remove.start as *mut c_void, + end: remove.end as *mut c_void, + }) + } + _ => Err(Error::UnrecognizedEvent(msg.event)), + } + } +} diff --git a/src/userfaultfd/src/lib.rs b/src/userfaultfd/src/lib.rs new file mode 100644 index 00000000000..5a60416522a --- /dev/null +++ b/src/userfaultfd/src/lib.rs @@ -0,0 +1,233 @@ +//! A Linux mechanism for handling page faults in user space. +//! +//! The main way to interact with this library is to create a `Uffd` object with a `UffdBuilder`, +//! then use the methods of `Uffd` from a worker thread. +//! +//! See [`userfaultfd(2)`](http://man7.org/linux/man-pages/man2/userfaultfd.2.html) and +//! [`ioctl_userfaultfd(2)`](http://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html) for more +//! details. + +mod builder; +mod error; +mod event; +mod raw; + +pub use crate::builder::{FeatureFlags, UffdBuilder}; +pub use crate::error::{Error, Result}; +pub use crate::event::Event; + +use bitflags::bitflags; +use libc::{self, c_void}; +use nix::errno::Errno; +use nix::unistd::read; +use std::mem; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; + +/// The userfaultfd object. +/// +/// The userspace representation of the object is a file descriptor, so this type implements +/// `AsRawFd`, `FromRawFd`, and `IntoRawFd`. These methods should be used with caution, but can be +/// essential for using functions like `poll` on a worker thread. +#[derive(Debug)] +pub struct Uffd { + fd: RawFd, +} + +impl Drop for Uffd { + fn drop(&mut self) { + unsafe { libc::close(self.fd) }; + } +} + +impl AsRawFd for Uffd { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl IntoRawFd for Uffd { + fn into_raw_fd(self) -> RawFd { + self.fd + } +} + +impl FromRawFd for Uffd { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Uffd { fd } + } +} + +impl Uffd { + /// Register a memory address range with the userfaultfd object, and returns the `IoctlFlags` + /// that are available for the selected range. + /// + /// While the underlying `ioctl` call accepts mode flags, only one mode + /// (`UFFDIO_REGISTER_MODE_MISSING`) is currently supported. + pub fn register(&self, start: *mut c_void, len: usize) -> Result { + let mut register = raw::uffdio_register { + range: raw::uffdio_range { + start: start as u64, + len: len as u64, + }, + // this is the only mode currently supported + mode: raw::UFFDIO_REGISTER_MODE_MISSING, + ioctls: 0, + }; + unsafe { + raw::register(self.as_raw_fd(), &mut register as *mut raw::uffdio_register)?; + } + IoctlFlags::from_bits(register.ioctls).ok_or(Error::UnrecognizedIoctls(register.ioctls)) + } + + /// Unregister a memory address range from the userfaultfd object. + pub fn unregister(&self, start: *mut c_void, len: usize) -> Result<()> { + let mut range = raw::uffdio_range { + start: start as u64, + len: len as u64, + }; + unsafe { + raw::unregister(self.as_raw_fd(), &mut range as *mut raw::uffdio_range)?; + } + Ok(()) + } + + /// Atomically copy a continuous memory chunk into the userfaultfd-registed range, and return + /// the number of bytes that were successfully copied. + /// + /// If `wake` is `true`, wake up the thread waiting for pagefault resolution on the memory + /// range. + pub unsafe fn copy( + &self, + src: *const c_void, + dst: *mut c_void, + len: usize, + wake: bool, + ) -> Result { + let mut copy = raw::uffdio_copy { + src: src as u64, + dst: dst as u64, + len: len as u64, + mode: if wake { + 0 + } else { + raw::UFFDIO_COPY_MODE_DONTWAKE + }, + copy: 0, + }; + + let res = raw::copy(self.as_raw_fd(), &mut copy as *mut raw::uffdio_copy); + if let Err(e) = res { + if let Some(errno) = e.as_errno() { + Err(Error::CopyFailed(errno)) + } else { + Err(e.into()) + } + } else { + if copy.copy < 0 { + // shouldn't ever get here, as errno should be caught above + Err(Error::CopyFailed(Errno::from_i32(-copy.copy as i32))) + } else { + Ok(copy.copy as usize) + } + } + } + + /// Zero out a memory address range registered with userfaultfd, and return the number of bytes + /// that were successfully zeroed. + /// + /// If `wake` is `true`, wake up the thread waiting for pagefault resolution on the memory + /// address range. + pub unsafe fn zeropage(&self, start: *mut c_void, len: usize, wake: bool) -> Result { + let mut zeropage = raw::uffdio_zeropage { + range: raw::uffdio_range { + start: start as u64, + len: len as u64, + }, + mode: if wake { + 0 + } else { + raw::UFFDIO_ZEROPAGE_MODE_DONTWAKE + }, + zeropage: 0, + }; + + let res = raw::zeropage(self.as_raw_fd(), &mut zeropage as &mut raw::uffdio_zeropage); + if let Err(e) = res { + if let Some(errno) = e.as_errno() { + Err(Error::ZeropageFailed(errno)) + } else { + Err(e.into()) + } + } else { + if zeropage.zeropage < 0 { + // shouldn't ever get here, as errno should be caught above + Err(Error::ZeropageFailed(Errno::from_i32( + -zeropage.zeropage as i32, + ))) + } else { + Ok(zeropage.zeropage as usize) + } + } + } + + /// Wake up the thread waiting for pagefault resolution on the specified memory address range. + pub fn wake(&self, start: *mut c_void, len: usize) -> Result<()> { + let mut range = raw::uffdio_range { + start: start as u64, + len: len as u64, + }; + unsafe { + raw::wake(self.as_raw_fd(), &mut range as *mut raw::uffdio_range)?; + } + Ok(()) + } + + /// Read an `Event` from the userfaultfd object. + /// + /// If the `Uffd` object was created with `non_blocking` set to `false`, this will block until + /// an event is successfully read (returning `Some(event)`, or an error is returned. + /// + /// If `non_blocking` was `true`, this will immediately return `None` if no event is ready to + /// read. + /// + /// Note that while this method doesn't require a mutable reference to the `Uffd` object, it + /// does consume bytes (thread-safely) from the underlying file descriptor. + pub fn read_event(&self) -> Result> { + const MSG_SIZE: usize = mem::size_of::(); + let mut buf = [0x00u8; MSG_SIZE]; + + // read one uffd_msg at a time; maybe implement an iterator for handling many at once? + let res = read(self.as_raw_fd(), &mut buf); + + match res { + Err(e) if e.as_errno() == Some(Errno::EAGAIN) => return Ok(None), + Err(e) => Err(e)?, + Ok(nread) => { + if nread == libc::EOF as usize { + return Err(Error::ReadEof); + } + if nread != MSG_SIZE { + return Err(Error::IncompleteMsg { + read: nread, + expected: MSG_SIZE, + }); + } + let msg = unsafe { *(buf.as_mut_ptr() as *mut raw::uffd_msg) }; + let event = Event::from_uffd_msg(&msg)?; + return Ok(Some(event)); + } + } + } +} + +bitflags! { + /// Used with `UffdBuilder` and `Uffd::register()` to determine which operations are available. + pub struct IoctlFlags: u64 { + const REGISTER = 1 << raw::_UFFDIO_REGISTER; + const UNREGISTER = 1 << raw::_UFFDIO_UNREGISTER; + const WAKE = 1 << raw::_UFFDIO_WAKE; + const COPY = 1 << raw::_UFFDIO_COPY; + const ZEROPAGE = 1 << raw::_UFFDIO_ZEROPAGE; + const API = 1 << raw::_UFFDIO_API; + } +} diff --git a/src/userfaultfd/src/raw.rs b/src/userfaultfd/src/raw.rs new file mode 100644 index 00000000000..5b80a82fa9e --- /dev/null +++ b/src/userfaultfd/src/raw.rs @@ -0,0 +1,18 @@ +use libc::{c_int, c_long, syscall, SYS_userfaultfd, INT_MAX}; +pub use userfaultfd_sys::*; + +pub unsafe fn userfaultfd(flags: c_int) -> c_int { + let fd = syscall(SYS_userfaultfd, flags as c_long); + if fd > INT_MAX as c_long { + panic!("fd doesn't fit in a c_int"); + } else { + fd as c_int + } +} + +nix::ioctl_readwrite!(api, UFFDIO, _UFFDIO_API, uffdio_api); +nix::ioctl_readwrite!(register, UFFDIO, _UFFDIO_REGISTER, uffdio_register); +nix::ioctl_read!(unregister, UFFDIO, _UFFDIO_UNREGISTER, uffdio_range); +nix::ioctl_read!(wake, UFFDIO, _UFFDIO_WAKE, uffdio_range); +nix::ioctl_readwrite!(copy, UFFDIO, _UFFDIO_COPY, uffdio_copy); +nix::ioctl_readwrite!(zeropage, UFFDIO, _UFFDIO_ZEROPAGE, uffdio_zeropage); diff --git a/src/userfaultfd/tests/manpage_example.rs b/src/userfaultfd/tests/manpage_example.rs new file mode 100644 index 00000000000..49a061fcbaa --- /dev/null +++ b/src/userfaultfd/tests/manpage_example.rs @@ -0,0 +1,8 @@ +#[test] +fn run_manpage_example() { + let output = std::process::Command::new("cargo") + .args(&["run", "--example", "manpage", "--", "3"]) + .output() + .expect("manpage example failed to start"); + assert!(output.status.success(), "manpage example succeeded"); +} diff --git a/src/userfaultfd/userfaultfd-sys/Cargo.toml b/src/userfaultfd/userfaultfd-sys/Cargo.toml new file mode 100644 index 00000000000..d76aadb4357 --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "userfaultfd-sys" +version = "0.2.1-dev" +authors = ["Adam C. Foltzer "] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "Low-level bindings for userfaultfd functionality on Linux." + +build = "build.rs" + +[dependencies] +cfg-if = "0.1" + +[build-dependencies] +bindgen = { version = "0.51", default-features = false } +cc = "1.0" + +[features] +default = [] +linux4_14 = [] diff --git a/src/userfaultfd/userfaultfd-sys/build.rs b/src/userfaultfd/userfaultfd-sys/build.rs new file mode 100644 index 00000000000..57b17f3db7d --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/build.rs @@ -0,0 +1,57 @@ +use bindgen; +use bindgen::callbacks::{IntKind, ParseCallbacks}; +use cc; +use std::env; +use std::path::PathBuf; + +fn main() { + generate_bindings(); + + cc::Build::new() + .file("src/consts.c") + .compile("userfaultfd_sys_consts"); +} + +fn generate_bindings() { + let bindings = bindgen::Builder::default() + .header("wrapper.h") + // filter out stuff from + .blacklist_item("__BITS_PER_LONG") + .blacklist_item("__FD_SETSIZE") + .blacklist_type("__[lb]e.*") + .blacklist_type("__w?sum.*") + .blacklist_type("__kernel_.*") + .parse_callbacks(Box::new(Callbacks {})) + .generate() + .expect("binding generation failed"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("binding file couldn't be written"); +} + +// all this stuff with callbacks is to give the integer constants the right types + +#[derive(Debug)] +struct Callbacks {} + +impl ParseCallbacks for Callbacks { + fn int_macro(&self, name: &str, _value: i64) -> Option { + for (prefix, kind) in [ + ("_UFFDIO_", IntKind::U64), + ("UFFD_API", IntKind::U64), + ("UFFDIO", IntKind::U8), + ("UFFD_EVENT_", IntKind::U8), + ("UFFD_PAGEFAULT_FLAG_", IntKind::U64), + ("UFFD_FEATURE_", IntKind::U64), + ] + .iter() + { + if name.starts_with(prefix) { + return Some(*kind); + } + } + return None; + } +} diff --git a/src/userfaultfd/userfaultfd-sys/src/consts.c b/src/userfaultfd/userfaultfd-sys/src/consts.c new file mode 100644 index 00000000000..310c9d41719 --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/src/consts.c @@ -0,0 +1,39 @@ +#include +#include + +#ifdef UFFD_API +const __u64 _const_UFFD_API = UFFD_API; +#endif + +#ifdef UFFD_API_FEATURES +const __u64 _const_UFFD_API_FEATURES = UFFD_API_FEATURES; +#endif + +#ifdef UFFD_API_IOCTLS +const __u64 _const_UFFD_API_IOCTLS = UFFD_API_IOCTLS; +#endif + +#ifdef UFFD_API_RANGE_IOCTLS +const __u64 _const_UFFD_API_RANGE_IOCTLS = UFFD_API_RANGE_IOCTLS; +#endif + +#ifdef UFFD_API_RANGE_IOCTLS_BASIC +const __u64 _const_UFFD_API_RANGE_IOCTLS_BASIC = UFFD_API_RANGE_IOCTLS_BASIC; +#endif + +#ifdef UFFDIO_REGISTER_MODE_MISSING +const __u64 _const_UFFDIO_REGISTER_MODE_MISSING = UFFDIO_REGISTER_MODE_MISSING; +#endif + +#ifdef UFFDIO_REGISTER_MODE_WP +const __u64 _const_UFFDIO_REGISTER_MODE_WP = UFFDIO_REGISTER_MODE_WP; +#endif + +#ifdef UFFDIO_COPY_MODE_DONTWAKE +const __u64 _const_UFFDIO_COPY_MODE_DONTWAKE = UFFDIO_COPY_MODE_DONTWAKE; +#endif + +#ifdef UFFDIO_ZEROPAGE_MODE_DONTWAKE +const __u64 _const_UFFDIO_ZEROPAGE_MODE_DONTWAKE = UFFDIO_ZEROPAGE_MODE_DONTWAKE; +#endif + diff --git a/src/userfaultfd/userfaultfd-sys/src/lib.rs b/src/userfaultfd/userfaultfd-sys/src/lib.rs new file mode 100644 index 00000000000..adddf5b3fcb --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/src/lib.rs @@ -0,0 +1,22 @@ +//! System bindings to `userfaultfd`. +//! +//! The minimum supported Linux kernel version is 4.11, but additional features from 4.14+ are +//! available by using the `linux4_14` Cargo feature. + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "linux4_14")] { + mod linux4_14; + pub use crate::linux4_14::*; + } else { + mod linux4_11; + pub use crate::linux4_11::*; + } +} + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/src/userfaultfd/userfaultfd-sys/src/linux4_11.rs b/src/userfaultfd/userfaultfd-sys/src/linux4_11.rs new file mode 100644 index 00000000000..a478f0041ca --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/src/linux4_11.rs @@ -0,0 +1,79 @@ +use super::*; + +// The following are preprocessor constants that bindgen can't figure out, so we enter them manually +// from , and have tests to make sure they're accurate. + +pub const UFFD_API: u64 = 0xAA; +pub const UFFD_API_FEATURES: u64 = UFFD_FEATURE_EVENT_FORK + | UFFD_FEATURE_EVENT_REMAP + | UFFD_FEATURE_EVENT_REMOVE + | UFFD_FEATURE_EVENT_UNMAP + | UFFD_FEATURE_MISSING_HUGETLBFS + | UFFD_FEATURE_MISSING_SHMEM; + +pub const UFFD_API_IOCTLS: u64 = 1 << _UFFDIO_REGISTER | 1 << _UFFDIO_UNREGISTER | 1 << _UFFDIO_API; +pub const UFFD_API_RANGE_IOCTLS: u64 = + 1 << _UFFDIO_WAKE | 1 << _UFFDIO_COPY | 1 << _UFFDIO_ZEROPAGE; + +pub const UFFDIO_REGISTER_MODE_MISSING: u64 = 1 << 0; +pub const UFFDIO_REGISTER_MODE_WP: u64 = 1 << 1; + +pub const UFFDIO_COPY_MODE_DONTWAKE: u64 = 1 << 0; + +pub const UFFDIO_ZEROPAGE_MODE_DONTWAKE: u64 = 1 << 0; + +#[cfg(test)] +mod const_tests { + use super::*; + + extern "C" { + #[no_mangle] + static _const_UFFD_API: u64; + #[no_mangle] + static _const_UFFD_API_FEATURES: u64; + #[no_mangle] + static _const_UFFD_API_IOCTLS: u64; + #[no_mangle] + static _const_UFFD_API_RANGE_IOCTLS: u64; + #[no_mangle] + static _const_UFFDIO_REGISTER_MODE_MISSING: u64; + #[no_mangle] + static _const_UFFDIO_REGISTER_MODE_WP: u64; + #[no_mangle] + static _const_UFFDIO_COPY_MODE_DONTWAKE: u64; + #[no_mangle] + static _const_UFFDIO_ZEROPAGE_MODE_DONTWAKE: u64; + } + + #[test] + fn consts_correct() { + unsafe { + assert_eq!(UFFD_API, _const_UFFD_API, "UFFD_API"); + assert_eq!( + UFFD_API_FEATURES, _const_UFFD_API_FEATURES, + "UFFD_API_FEATURES" + ); + assert_eq!(UFFD_API_IOCTLS, _const_UFFD_API_IOCTLS, "UFFD_API_IOCTLS"); + assert_eq!( + UFFD_API_RANGE_IOCTLS, _const_UFFD_API_RANGE_IOCTLS, + "UFFD_API_RANGE_IOCTLS" + ); + assert_eq!( + UFFDIO_REGISTER_MODE_MISSING, _const_UFFDIO_REGISTER_MODE_MISSING, + "UFFDIO_REGISTER_MODE_MISSING" + ); + assert_eq!( + UFFDIO_REGISTER_MODE_WP, _const_UFFDIO_REGISTER_MODE_WP, + "UFFDIO_REGISTER_MODE_WP" + ); + assert_eq!( + UFFDIO_COPY_MODE_DONTWAKE, _const_UFFDIO_COPY_MODE_DONTWAKE, + "UFFDIO_COPY_MODE_DONTWAKE" + ); + assert_eq!( + UFFDIO_ZEROPAGE_MODE_DONTWAKE, _const_UFFDIO_ZEROPAGE_MODE_DONTWAKE, + "UFFDIO_ZEROPAGE_MODE_DONTWAKE" + ); + } + } +} diff --git a/src/userfaultfd/userfaultfd-sys/src/linux4_14.rs b/src/userfaultfd/userfaultfd-sys/src/linux4_14.rs new file mode 100644 index 00000000000..0bcfee70c30 --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/src/linux4_14.rs @@ -0,0 +1,88 @@ +use super::*; + +// The following are preprocessor constants that bindgen can't figure out, so we enter them manually +// from , and have tests to make sure they're accurate. + +pub const UFFD_API: u64 = 0xAA; + +pub const UFFD_API_FEATURES: u64 = UFFD_FEATURE_EVENT_FORK + | UFFD_FEATURE_EVENT_REMAP + | UFFD_FEATURE_EVENT_REMOVE + | UFFD_FEATURE_EVENT_UNMAP + | UFFD_FEATURE_MISSING_HUGETLBFS + | UFFD_FEATURE_MISSING_SHMEM + | UFFD_FEATURE_SIGBUS + | UFFD_FEATURE_THREAD_ID; +pub const UFFD_API_IOCTLS: u64 = 1 << _UFFDIO_REGISTER | 1 << _UFFDIO_UNREGISTER | 1 << _UFFDIO_API; +pub const UFFD_API_RANGE_IOCTLS: u64 = + 1 << _UFFDIO_WAKE | 1 << _UFFDIO_COPY | 1 << _UFFDIO_ZEROPAGE; +pub const UFFD_API_RANGE_IOCTLS_BASIC: u64 = 1 << _UFFDIO_WAKE | 1 << _UFFDIO_COPY; + +pub const UFFDIO_REGISTER_MODE_MISSING: u64 = 1 << 0; +pub const UFFDIO_REGISTER_MODE_WP: u64 = 1 << 1; + +pub const UFFDIO_COPY_MODE_DONTWAKE: u64 = 1 << 0; + +pub const UFFDIO_ZEROPAGE_MODE_DONTWAKE: u64 = 1 << 0; + +#[cfg(test)] +mod const_tests { + use super::*; + + extern "C" { + #[no_mangle] + static _const_UFFD_API: u64; + #[no_mangle] + static _const_UFFD_API_FEATURES: u64; + #[no_mangle] + static _const_UFFD_API_IOCTLS: u64; + #[no_mangle] + static _const_UFFD_API_RANGE_IOCTLS: u64; + #[no_mangle] + static _const_UFFD_API_RANGE_IOCTLS_BASIC: u64; + #[no_mangle] + static _const_UFFDIO_REGISTER_MODE_MISSING: u64; + #[no_mangle] + static _const_UFFDIO_REGISTER_MODE_WP: u64; + #[no_mangle] + static _const_UFFDIO_COPY_MODE_DONTWAKE: u64; + #[no_mangle] + static _const_UFFDIO_ZEROPAGE_MODE_DONTWAKE: u64; + } + + #[test] + fn consts_correct() { + unsafe { + assert_eq!(UFFD_API, _const_UFFD_API, "UFFD_API"); + assert_eq!( + UFFD_API_FEATURES, _const_UFFD_API_FEATURES, + "UFFD_API_FEATURES" + ); + assert_eq!(UFFD_API_IOCTLS, _const_UFFD_API_IOCTLS, "UFFD_API_IOCTLS"); + assert_eq!( + UFFD_API_RANGE_IOCTLS, _const_UFFD_API_RANGE_IOCTLS, + "UFFD_API_RANGE_IOCTLS" + ); + assert_eq!( + UFFD_API_RANGE_IOCTLS_BASIC, _const_UFFD_API_RANGE_IOCTLS_BASIC, + "UFFD_API_RANGE_IOCTLS_BASIC" + ); + assert_eq!( + UFFDIO_REGISTER_MODE_MISSING, _const_UFFDIO_REGISTER_MODE_MISSING, + "UFFDIO_REGISTER_MODE_MISSING" + ); + assert_eq!( + UFFDIO_REGISTER_MODE_WP, _const_UFFDIO_REGISTER_MODE_WP, + "UFFDIO_REGISTER_MODE_WP" + ); + assert_eq!( + UFFDIO_COPY_MODE_DONTWAKE, _const_UFFDIO_COPY_MODE_DONTWAKE, + "UFFDIO_COPY_MODE_DONTWAKE" + ); + assert_eq!( + UFFDIO_ZEROPAGE_MODE_DONTWAKE, _const_UFFDIO_ZEROPAGE_MODE_DONTWAKE, + "UFFDIO_ZEROPAGE_MODE_DONTWAKE" + ); + } + } +} diff --git a/src/userfaultfd/userfaultfd-sys/wrapper.h b/src/userfaultfd/userfaultfd-sys/wrapper.h new file mode 100644 index 00000000000..8f63603cdb1 --- /dev/null +++ b/src/userfaultfd/userfaultfd-sys/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index eb1cfe8000b..4973394887d 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -25,6 +25,10 @@ seccomp = { path = "../seccomp" } snapshot = { path = "../snapshot"} utils = { path = "../utils" } +userfaultfd = { path = "../userfaultfd" } +passfd = { path = "../passfd" } + + [target.'cfg(target_arch = "x86_64")'.dependencies] cpuid = { path = "../cpuid" } diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index 3e44193e90d..89089603827 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -10,6 +10,10 @@ //! machine (microVM). #![deny(missing_docs)] +// crates for userfaultfd +extern crate userfaultfd; +extern crate passfd; + /// Handles setup and initialization a `Vmm` object. pub mod builder; /// Syscalls allowed through the seccomp filter. diff --git a/src/vmm/src/memory_snapshot.rs b/src/vmm/src/memory_snapshot.rs index 7476ac248b4..df049546b64 100644 --- a/src/vmm/src/memory_snapshot.rs +++ b/src/vmm/src/memory_snapshot.rs @@ -7,6 +7,14 @@ use std::fmt::{Display, Formatter}; use std::fs::File; use std::io::SeekFrom; +use logger::info; +// for userfaultfd +use std::path::PathBuf; +use std::os::unix::io::AsRawFd; +use std::os::unix::net::UnixListener; +use userfaultfd::UffdBuilder; +use passfd::FdPassingExt; + use versionize::{VersionMap, Versionize, VersionizeResult}; use versionize_derive::Versionize; use vm_memory::{ @@ -58,7 +66,10 @@ where file: &File, state: &GuestMemoryState, track_dirty_pages: bool, + enable_user_page_faults: bool, ) -> std::result::Result; + /// Registers guest memory for hanlding page faults with an external user-level process + fn register_for_upf(&self, sock_file_path: &PathBuf) -> std::result::Result<(), Error>; } /// Errors associated with dumping guest memory to file. @@ -74,6 +85,8 @@ pub enum Error { PageSize(errno::Error), /// Cannot dump memory. WriteMemory(GuestMemoryError), + /// Cannot register region for user page fault handling. + UserPageFault(userfaultfd::Error), } impl Display for Error { @@ -85,6 +98,7 @@ impl Display for Error { CreateRegion(err) => write!(f, "Cannot create memory region: {:?}", err), PageSize(err) => write!(f, "Cannot fetch system's page size: {:?}", err), WriteMemory(err) => write!(f, "Cannot dump memory: {:?}", err), + UserPageFault(err) => write!(f, "Cannot register memory for uPF: {:?}", err), } } } @@ -175,9 +189,15 @@ impl SnapshotMemory for GuestMemoryMmap { file: &File, state: &GuestMemoryState, track_dirty_pages: bool, + enable_user_page_faults: bool, ) -> std::result::Result { let mut mmap_regions = Vec::new(); + let mut prot = libc::MAP_NORESERVE | libc::MAP_PRIVATE; + if enable_user_page_faults { + prot = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS; + } for region in state.regions.iter() { + // userfaultfd requires allocating anonymous memory let mmap_region = GuestRegionMmap::build_guarded( Some(FileOffset::new( file.try_clone().map_err(Error::FileHandle)?, @@ -185,7 +205,7 @@ impl SnapshotMemory for GuestMemoryMmap { )), region.size, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_NORESERVE | libc::MAP_PRIVATE, + prot, ) .map(|r| { let mut region = GuestRegionMmap::new(r, GuestAddress(region.base_address))?; @@ -202,6 +222,42 @@ impl SnapshotMemory for GuestMemoryMmap { Ok(Self::from_regions(mmap_regions).map_err(Error::CreateMemory)?) } + + /// Registers guest memory regions for handling page faults + /// with an external user-level process. + fn register_for_upf(&self, sock_file_path: &PathBuf) -> std::result::Result<(), Error> { + self.with_regions(|_, region| { + info!("Guest memory size={:?}MB, base_address={:?}, last_addr={:?}", + region.len()/1024/1024, + region.get_host_address(region.to_region_addr(region.start_addr()).unwrap()), + region.get_host_address(region.to_region_addr(region.last_addr()).unwrap())); + + let uffd = UffdBuilder::new() + .close_on_exec(true) + .non_blocking(true) + .create() + .expect("uffd creation"); + + let addr = region.get_host_address(region.to_region_addr(region.start_addr()).unwrap()).unwrap(); + let len = region.len(); + info!("Host address of the region's start = {:p}, len={:?}", addr, len); + uffd.register(addr as *mut u8 as _, len as u64 as _).expect("uffd.register()"); + + let listener = UnixListener::bind(sock_file_path).unwrap(); + let (stream, _) = listener.accept().unwrap(); + stream.send_fd(uffd.as_raw_fd()).unwrap(); + + info!("Sent the fd!"); + + // Cause a page fault on the first page to communicate the start_addr's hVA + unsafe{ + print!("after reg: ptr={:p}, mem value = {:?}, len={:?}", addr, *addr, len) + } + + Ok(()) + }) + .map_err(Error::UserPageFault) + } } fn get_page_size() -> Result { diff --git a/src/vmm/src/persist.rs b/src/vmm/src/persist.rs index 7dc67ff083d..89c30d072a3 100644 --- a/src/vmm/src/persist.rs +++ b/src/vmm/src/persist.rs @@ -174,6 +174,8 @@ pub enum LoadSnapshotError { CpuVendorMismatch(String), /// Snapshot failed sanity checks. InvalidSnapshot(String), + /// Failed to register guest memory for user page fault handling. + UserPageFault(memory_snapshot::Error), } impl Display for LoadSnapshotError { @@ -189,6 +191,7 @@ impl Display for LoadSnapshotError { SnapshotBackingFileMetadata(err) => write!(f, "Cannot retrieve file metadata: {}", err), CpuVendorMismatch(err) => write!(f, "Snapshot cpu vendor mismatch: {}", err), InvalidSnapshot(err) => write!(f, "Snapshot sanity check failed: {}", err), + UserPageFault(err) => write!(f, "Cannot register memory for uPF: {:?}", err), } } } @@ -395,7 +398,11 @@ pub fn restore_from_snapshot( ¶ms.mem_file_path, µvm_state.memory_state, track_dirty_pages, + params.enable_user_page_faults, )?; + if params.enable_user_page_faults == true { + guest_memory.register_for_upf(¶ms.sock_file_path).map_err(UserPageFault)?; + } builder::build_microvm_from_snapshot( event_manager, microvm_state, @@ -423,10 +430,11 @@ fn guest_memory_from_file( mem_file_path: &PathBuf, mem_state: &GuestMemoryState, track_dirty_pages: bool, + enable_user_page_faults: bool, ) -> std::result::Result { use self::LoadSnapshotError::{DeserializeMemory, MemoryBackingFile}; let mem_file = File::open(mem_file_path).map_err(MemoryBackingFile)?; - GuestMemoryMmap::restore(&mem_file, mem_state, track_dirty_pages).map_err(DeserializeMemory) + GuestMemoryMmap::restore(&mem_file, mem_state, track_dirty_pages, enable_user_page_faults).map_err(DeserializeMemory) } #[cfg(target_arch = "x86_64")] diff --git a/src/vmm/src/vmm_config/snapshot.rs b/src/vmm/src/vmm_config/snapshot.rs index 9662de08cd3..a140899e777 100644 --- a/src/vmm/src/vmm_config/snapshot.rs +++ b/src/vmm/src/vmm_config/snapshot.rs @@ -48,6 +48,10 @@ pub struct LoadSnapshotParams { pub snapshot_path: PathBuf, /// Path to the file that contains the guest memory to be loaded. pub mem_file_path: PathBuf, + /// Setting this flag enables user page faults handling by a different process. + pub enable_user_page_faults: bool, + /// Path to the passfd socket. + pub sock_file_path: PathBuf, /// Setting this flag will enable KVM dirty page tracking and will /// allow taking subsequent incremental snapshots. #[serde(default)] diff --git a/tools/devctr/Dockerfile.x86_64 b/tools/devctr/Dockerfile.x86_64 index 12d1869954a..712cc395b28 100644 --- a/tools/devctr/Dockerfile.x86_64 +++ b/tools/devctr/Dockerfile.x86_64 @@ -52,6 +52,11 @@ RUN apt-get update \ screen \ tzdata \ npm \ + clang \ + llvm \ + musl \ + musl-dev \ + musl-tools \ && python3 -m pip install \ setuptools \ wheel \ @@ -100,5 +105,12 @@ RUN mkdir "$TMP_BUILD_DIR" && cd "$TMP_BUILD_DIR" \ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION_TAG}/tini-static-amd64 /sbin/tini RUN chmod +x /sbin/tini +# Workaround for musl-gcc to build userfaulfd-sys (musl cannot find linux headers) +RUN cd /usr/include/x86_64-linux-musl \ + && ln -s ../x86_64-linux-gnu/asm asm \ + && ln -s ../linux linux \ + && ln -s ../asm-generic asm-generic + + WORKDIR "$FIRECRACKER_SRC_DIR" ENTRYPOINT ["/sbin/tini", "--"] diff --git a/tools/devtool b/tools/devtool index 75283dfaecb..fd6f08dde70 100755 --- a/tools/devtool +++ b/tools/devtool @@ -69,7 +69,7 @@ # would help with reproducible builds (in addition to pinning Cargo.lock) # Development container image (without tag) -DEVCTR_IMAGE_NO_TAG="public.ecr.aws/firecracker/fcuvm" +DEVCTR_IMAGE_NO_TAG="docker.io/vhiveease/fcuvm_dev" # Development container tag DEVCTR_IMAGE_TAG="v28" @@ -243,8 +243,6 @@ ensure_devctr() { # Check if we have the container image available locally. Attempt to # download it, if we don't. [[ $(docker images -q "$DEVCTR_IMAGE" | wc -l) -gt 0 ]] || { - say "About to pull docker image $DEVCTR_IMAGE" - get_user_confirmation || die "Aborted." # Run docker pull 5 times in case it fails - sleep 3 seconds # between attempts