From 7da58637c25a84f6b239a548fbad7b10abd56f3e Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:36:51 +0800 Subject: [PATCH] fix(deps): upgrade crates with security advisories (#338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo deny check now passes, with a deny.toml at the repository root (advisories, licenses, bans, sources). - zip 2.4.2 → 9 as one workspace dependency (RUSTSEC-2026-0367/0368/0369). Our own extraction is hardened too: tar/zip entries cannot escape the destination, symlinks are allowed only when they resolve inside it, entries cannot traverse a symlink, and setuid/setgid/sticky bits are dropped (also after system tar). - tiberius 0.12 → 0.13 with rustls 0.23, which brings the fixed rustls / rustls-webpki (RUSTSEC-2026-0098/0099/0104/0346/0349/0382/0383). The bb8-tiberius adapter is replaced by a small bb8 connection manager in ide-db that keeps its single-redirect handling. - crypto-bigint / ctutils updated (RUSTSEC-2026-0385/0387). - rsa RUSTSEC-2023-0071 (no fixed release) is ignored with its reason: RSA is used only for MySQL password encryption and SSH signatures, never to decrypt attacker-controlled ciphertext. - Unmaintained transitive crates (paste, proc-macro-error, rustls-pemfile, zbus/zvariant) remain warnings. MPL-2.0 is allowed (file-level copyleft, dependencies used unmodified). 未在真实 SQL Server 及 Windows / Linux 上验证解压。 --- Cargo.lock | 308 +++++++--------- Cargo.toml | 4 +- crates/ide-db/Cargo.toml | 2 +- crates/ide-db/src/conn.rs | 88 +++-- crates/ide-run/Cargo.toml | 2 +- crates/ide-sdk/Cargo.toml | 2 +- crates/ide-tools/Cargo.toml | 2 +- crates/ide-tools/src/archive.rs | 340 +++++++++++++++++- .../ide-tools/tests/fixtures/escape.tar.bz2 | Bin 0 -> 189 bytes crates/ide-tools/tests/fixtures/escape.tar.xz | Bin 0 -> 228 bytes .../tests/fixtures/generate_escape.py | 27 ++ crates/ide-vfs/Cargo.toml | 2 +- crates/ide-vfs/src/archive.rs | 3 +- crates/ide-workspace/Cargo.toml | 2 +- deny.toml | 26 ++ 15 files changed, 584 insertions(+), 224 deletions(-) create mode 100644 crates/ide-tools/tests/fixtures/escape.tar.bz2 create mode 100644 crates/ide-tools/tests/fixtures/escape.tar.xz create mode 100644 crates/ide-tools/tests/fixtures/generate_escape.py create mode 100644 deny.toml diff --git a/Cargo.lock b/Cargo.lock index db8d3b97..b73037d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,6 @@ name = "arbitrary" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" -dependencies = [ - "derive_arbitrary", -] [[package]] name = "arc-swap" @@ -314,19 +311,6 @@ dependencies = [ "syn 3.0.9", ] -[[package]] -name = "asynchronous-codec" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" -dependencies = [ - "bytes", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite", -] - [[package]] name = "asynchronous-codec" version = "0.7.0" @@ -384,6 +368,29 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "base16ct" version = "1.0.0" @@ -426,19 +433,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "bb8-tiberius" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a5023b318f47a294d02b46ce0d82ac8e7df0afce2257f041ba7316566d6cd56" -dependencies = [ - "bb8", - "thiserror 1.0.69", - "tiberius", - "tokio", - "tokio-util", -] - [[package]] name = "bcrypt-pbkdf" version = "0.11.0" @@ -799,6 +793,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -958,6 +954,15 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cmov" version = "0.5.4" @@ -1393,9 +1398,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" -version = "0.7.5" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" +checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11" dependencies = [ "cpubits", "ctutils", @@ -1479,9 +1484,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a" dependencies = [ "cmov", "subtle", @@ -1685,17 +1690,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derive_arbitrary" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -2295,6 +2289,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -2399,6 +2399,12 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.34" @@ -3874,9 +3880,9 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.46", + "rustls", "tokio", - "tokio-rustls 0.26.6", + "tokio-rustls", "tower-service", ] @@ -4142,7 +4148,6 @@ name = "ide-db" version = "0.1.26" dependencies = [ "bb8", - "bb8-tiberius", "dirs 6.0.0", "futures", "ide-core", @@ -4155,6 +4160,7 @@ dependencies = [ "tempfile", "tiberius", "tokio", + "tokio-util", "toml 0.9.12+spec-1.1.0", "tracing", ] @@ -4250,7 +4256,7 @@ dependencies = [ "ide-core", "parking_lot", "reqwest", - "rustls 0.23.46", + "rustls", "rustls-platform-verifier", "serde", "serde_json", @@ -4465,7 +4471,7 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", "toml_edit 0.25.17+spec-1.1.0", "tracing", - "zip 2.4.2", + "zip", ] [[package]] @@ -4485,7 +4491,7 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", "tracing", "which", - "zip 2.4.2", + "zip", ] [[package]] @@ -4598,7 +4604,7 @@ dependencies = [ "tempfile", "tracing", "which", - "zip 2.4.2", + "zip", ] [[package]] @@ -4639,7 +4645,7 @@ dependencies = [ "tempfile", "tracing", "trash", - "zip 2.4.2", + "zip", ] [[package]] @@ -4677,7 +4683,7 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", "toml_edit 0.25.17+spec-1.1.0", "tracing", - "zip 2.4.2", + "zip", ] [[package]] @@ -5046,6 +5052,16 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.106" @@ -6179,12 +6195,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -6663,9 +6673,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty-hex" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" +checksum = "9a65843dfefbafd3c879c683306959a6de478443ffe9c9adf02f5976432402d7" [[package]] name = "primefield" @@ -7061,14 +7071,14 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.23.46", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", - "tokio-rustls 0.26.6", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -7330,64 +7340,32 @@ dependencies = [ "rustix", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.16", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", + "security-framework", ] [[package]] @@ -7410,11 +7388,11 @@ dependencies = [ "jni 0.22.4", "log", "once_cell", - "rustls 0.23.46", - "rustls-native-certs 0.8.4", + "rustls", + "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.16", - "security-framework 3.7.0", + "rustls-webpki", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -7426,22 +7404,13 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -7578,16 +7547,6 @@ dependencies = [ "sha2 0.11.1", ] -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sdd" version = "4.8.11" @@ -7612,16 +7571,12 @@ dependencies = [ ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "secrecy" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", + "zeroize", ] [[package]] @@ -8226,7 +8181,7 @@ dependencies = [ "log", "memchr", "percent-encoding", - "rustls 0.23.46", + "rustls", "serde", "serde_json", "sha2 0.10.9", @@ -8915,11 +8870,11 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259989d4fe11b2bf51f7d671642249b1837c4ae2da1430dc713ae38f98ceeec" +checksum = "d957abbcb3f9f6c76d22a3a0f012f30880640d0bccb5634d2b12a9937e4c872b" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "dirs 7.0.0", "flate2", "futures-util", @@ -8931,7 +8886,7 @@ dependencies = [ "osakit", "percent-encoding", "reqwest", - "rustls 0.23.46", + "rustls", "semver", "serde", "serde_json", @@ -8944,7 +8899,7 @@ dependencies = [ "tokio", "url", "windows-sys 0.61.2", - "zip 4.6.1", + "zip", ] [[package]] @@ -9175,30 +9130,47 @@ dependencies = [ [[package]] name = "tiberius" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1446cb4198848d1562301a3340424b4f425ef79f35ef9ee034769a9dd92c10d" +checksum = "e07324791de2bdaed058af4aa433a0b5ec5c0beac407a9797419c19805df2415" dependencies = [ "async-trait", - "asynchronous-codec 0.6.2", + "asynchronous-codec", "byteorder", "bytes", "connection-string", "encoding_rs", "enumflags2", + "futures-timer", "futures-util", + "libc", "num-traits", "once_cell", "pin-project-lite", "pretty-hex", - "rustls-native-certs 0.6.3", - "rustls-pemfile", - "thiserror 1.0.69", + "rustls-native-certs", + "rustls-pki-types", + "rustls-webpki", + "secrecy", + "thiserror 2.0.21", + "tiberius-macros", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-util", "tracing", "uuid", + "zeroize", +] + +[[package]] +name = "tiberius-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907fed5ea55695d902cc3359fffa4a35021dfa9b95ab4eb9c8376d42ffb9eec8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -9285,23 +9257,13 @@ dependencies = [ "syn 3.0.9", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ - "rustls 0.23.46", + "rustls", "tokio", ] @@ -10023,6 +9985,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + [[package]] name = "typeid" version = "1.0.3" @@ -10689,9 +10657,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ "rustls-pki-types", ] @@ -11521,7 +11489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb2c254fd8f366755335c9e43b865f8484fe3bd717d65ffe7c3f28852863030" dependencies = [ "async-trait", - "asynchronous-codec 0.7.0", + "asynchronous-codec", "bytes", "crossbeam-queue", "futures", @@ -11574,33 +11542,21 @@ dependencies = [ [[package]] name = "zip" -version = "2.4.2" +version = "9.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb" dependencies = [ - "arbitrary", "crc32fast", - "crossbeam-utils", - "displaydoc", "flate2", + "getrandom 0.4.3", "indexmap 2.14.2", "memchr", - "thiserror 2.0.21", + "nix 0.31.3", + "typed-path", + "windows-sys 0.61.2", "zopfli", ] -[[package]] -name = "zip" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" -dependencies = [ - "arbitrary", - "crc32fast", - "indexmap 2.14.2", - "memchr", -] - [[package]] name = "zlib-rs" version = "0.6.8" diff --git a/Cargo.toml b/Cargo.toml index b306c06a..12bedb8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ trash = "5" blake3 = "1" dunce = "1" dirs = "6" +zip = { version = "9", default-features = false, features = ["deflate"] } # search, index, language grep-searcher = "0.1" @@ -96,9 +97,8 @@ reqwest = { version = "0.13", default-features = false, features = ["rustls-no-p rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] } rustls-platform-verifier = "0.7" sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-ring-webpki", "sqlite", "postgres", "mysql"] } -tiberius = { version = "0.12", default-features = false, features = ["tds73", "rustls", "sql-browser-tokio"] } +tiberius = { version = "0.13", default-features = false, features = ["tds73", "rustls", "sql-browser-tokio"] } bb8 = "0.9" -bb8-tiberius = { version = "0.16", default-features = false, features = ["tds73", "tls", "with-tokio", "sql-browser"] } uuid = { version = "1", features = ["v4"] } # testing diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml index b1329a9c..6a5cb440 100644 --- a/crates/ide-db/Cargo.toml +++ b/crates/ide-db/Cargo.toml @@ -18,7 +18,7 @@ futures.workspace = true sqlx.workspace = true tiberius.workspace = true bb8.workspace = true -bb8-tiberius.workspace = true +tokio-util = { workspace = true, features = ["compat"] } dirs.workspace = true ide-ssh.workspace = true diff --git a/crates/ide-db/src/conn.rs b/crates/ide-db/src/conn.rs index 97e554f6..aa9fd7f4 100644 --- a/crates/ide-db/src/conn.rs +++ b/crates/ide-db/src/conn.rs @@ -47,13 +47,15 @@ pub struct QueryResult { pub execution_ms: f64, } -// bb8-tiberius never marks connections broken; keep an empty slot when discarded. -pub struct MssqlConnectionManager(bb8_tiberius::ConnectionManager); +// Keep an empty slot when a connection is discarded after cancellation or rollback failure. +pub struct MssqlConnectionManager(tiberius::Config); -pub struct MssqlConnection(Option); +type MssqlClient = tiberius::Client>; + +pub struct MssqlConnection(Option); impl std::ops::Deref for MssqlConnection { - type Target = bb8_tiberius::rt::Client; + type Target = MssqlClient; fn deref(&self) -> &Self::Target { self.0.as_ref().expect("live SQL Server connection") } @@ -67,10 +69,26 @@ impl std::ops::DerefMut for MssqlConnection { impl ManageConnection for MssqlConnectionManager { type Connection = MssqlConnection; - type Error = bb8_tiberius::Error; + type Error = tiberius::error::Error; async fn connect(&self) -> Result { - self.0.connect().await.map(|c| MssqlConnection(Some(c))) + use tokio_util::compat::TokioAsyncWriteCompatExt; + + let tcp = tokio::net::TcpStream::connect(self.0.get_addr()).await?; + tcp.set_nodelay(true)?; + let client = match MssqlClient::connect(self.0.clone(), tcp.compat_write()).await { + // Preserve the single redirect supported by the previous pool adapter. + Err(tiberius::error::Error::Routing { host, port }) => { + let mut config = self.0.clone(); + config.host(host); + config.port(port); + let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?; + tcp.set_nodelay(true)?; + MssqlClient::connect(config, tcp.compat_write()).await? + } + result => result?, + }; + Ok(MssqlConnection(Some(client))) } async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> { @@ -120,28 +138,25 @@ pub(crate) fn db_err(e: sqlx::Error) -> IdeError { } } -pub(crate) fn mssql_err(e: impl Into) -> IdeError { - match e.into() { - bb8_tiberius::Error::Tiberius(e) => match &e { - tiberius::error::Error::Server(d) => { - let code = d.code(); - // Wrong login: SQL Server 18456. - let auth = matches!(code, 18452 | 18456 | 18470); - IdeError::new( - if auth { ErrorKind::PermissionDenied } else { ErrorKind::InvalidInput }, - format!("[{code}] {}", d.message()), - ) - } - tiberius::error::Error::Io { .. } | tiberius::error::Error::Tls(_) => { - IdeError::new(ErrorKind::Io, e.to_string()) - } - _ => IdeError::new(ErrorKind::Protocol, e.to_string()), - }, - bb8_tiberius::Error::Io(e) => IdeError::new(ErrorKind::Io, e.to_string()), +pub(crate) fn mssql_err(e: tiberius::error::Error) -> IdeError { + match &e { + tiberius::error::Error::Server(d) => { + let code = d.code(); + // Wrong login: SQL Server 18456. + let auth = matches!(code, 18452 | 18456 | 18470); + IdeError::new( + if auth { ErrorKind::PermissionDenied } else { ErrorKind::InvalidInput }, + format!("[{code}] {}", d.message()), + ) + } + tiberius::error::Error::Io { .. } | tiberius::error::Error::Tls(_) => { + IdeError::new(ErrorKind::Io, e.to_string()) + } + _ => IdeError::new(ErrorKind::Protocol, e.to_string()), } } -fn mssql_pool_err(e: bb8::RunError) -> IdeError { +fn mssql_pool_err(e: bb8::RunError) -> IdeError { match e { bb8::RunError::User(e) => mssql_err(e), bb8::RunError::TimedOut => IdeError::new(ErrorKind::Io, "timed out connecting to the database"), @@ -364,13 +379,13 @@ macro_rules! fetch { /// Columns, rows, rows affected, more rows beyond the limit, time of the first answer. type Fetched = (Vec, Vec>>, Option, bool, Option); -async fn mssql_command(conn: &mut bb8_tiberius::rt::Client, sql: &str) -> IdeResult<()> { +async fn mssql_command(conn: &mut MssqlClient, sql: &str) -> IdeResult<()> { // Transaction control must be a batch: sp_executesql rejects a changed transaction count. conn.simple_query(sql).await.map_err(mssql_err)?.into_results().await.map_err(mssql_err)?; Ok(()) } -async fn mssql_fetch(conn: &mut bb8_tiberius::rt::Client, sql: &str, max: usize) -> IdeResult { +async fn mssql_fetch(conn: &mut MssqlClient, sql: &str, max: usize) -> IdeResult { let mut stream = conn.simple_query(sql).await.map_err(mssql_err)?; let first = Some(Instant::now()); let columns = stream @@ -669,7 +684,7 @@ impl Pool { if matches!(source.ssl, DbSslMode::Disable | DbSslMode::Prefer) { config.trust_cert(); } - let manager = MssqlConnectionManager(bb8_tiberius::ConnectionManager::new(config)); + let manager = MssqlConnectionManager(config); let pool = bb8::Pool::builder() .max_size(MAX) .connection_timeout(timeout) @@ -908,6 +923,23 @@ mod tests { use super::*; use crate::source::{DbOptions, DbProperty}; + #[test] + fn discarded_sqlserver_connections_are_broken() { + let manager = MssqlConnectionManager(tiberius::Config::new()); + assert!(manager.has_broken(&mut MssqlConnection(None))); + } + + #[tokio::test] + async fn sqlserver_connect_reports_io_errors() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let mut config = tiberius::Config::new(); + config.host("127.0.0.1"); + config.port(listener.local_addr().unwrap().port()); + drop(listener); + let result = MssqlConnectionManager(config).connect().await; + assert!(matches!(result, Err(tiberius::error::Error::Io { .. }))); + } + #[test] fn sqlserver_cells_preserve_values_and_nulls() { use std::borrow::Cow; diff --git a/crates/ide-run/Cargo.toml b/crates/ide-run/Cargo.toml index 955fd355..bacdf07f 100644 --- a/crates/ide-run/Cargo.toml +++ b/crates/ide-run/Cargo.toml @@ -17,7 +17,7 @@ tokio.workspace = true portable-pty.workspace = true parking_lot.workspace = true tracing.workspace = true -zip = { version = "=2.4.2", default-features = false, features = ["deflate"] } +zip.workspace = true [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/crates/ide-sdk/Cargo.toml b/crates/ide-sdk/Cargo.toml index a85096ad..d4225ada 100644 --- a/crates/ide-sdk/Cargo.toml +++ b/crates/ide-sdk/Cargo.toml @@ -18,7 +18,7 @@ specta.workspace = true toml.workspace = true tracing.workspace = true which = "8" -zip = { version = "=2.4.2", default-features = false, features = ["deflate"] } +zip.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/ide-tools/Cargo.toml b/crates/ide-tools/Cargo.toml index 9d9286ab..dadce894 100644 --- a/crates/ide-tools/Cargo.toml +++ b/crates/ide-tools/Cargo.toml @@ -16,7 +16,7 @@ tracing.workspace = true which = "8" flate2 = "1" tar = "0.4" -zip = { version = "=2.4.2", default-features = false, features = ["deflate"] } +zip.workspace = true sha2 = "0.10" hex = "0.4" diff --git a/crates/ide-tools/src/archive.rs b/crates/ide-tools/src/archive.rs index 7d8590b7..11a36926 100644 --- a/crates/ide-tools/src/archive.rs +++ b/crates/ide-tools/src/archive.rs @@ -65,6 +65,7 @@ pub fn extract( ArchiveKind::TarXz => extract_with_tar(archive, dest, "-xJf", cancel), ArchiveKind::TarBz2 => extract_with_tar(archive, dest, "-xjf", cancel), ArchiveKind::Gz => { + validate_single_name(dest, single_name)?; let mut gz = flate2::read::GzDecoder::new(BufReader::new(File::open(archive)?)); let target = dest.join(single_name); let mut out = File::create(&target)?; @@ -72,6 +73,7 @@ pub fn extract( make_executable(&target) } ArchiveKind::Raw => { + validate_single_name(dest, single_name)?; let target = dest.join(single_name); fs::copy(archive, &target)?; make_executable(&target) @@ -80,15 +82,25 @@ pub fn extract( } fn extract_tar(mut archive: tar::Archive, dest: &Path, cancel: &CancellationToken) -> IdeResult<()> { - archive.set_preserve_permissions(true); + archive.set_preserve_permissions(false); archive.set_overwrite(true); - for entry in archive.entries().map_err(|e| io_err("reading archive", e))? { - check(cancel)?; - let mut entry = entry.map_err(|e| io_err("reading archive", e))?; - // `unpack_in` refuses entries escaping `dest` (returns Ok(false)). - entry.unpack_in(dest).map_err(|e| io_err("extracting", e))?; - } - Ok(()) + let result = (|| { + for entry in archive.entries().map_err(|e| io_err("reading archive", e))? { + check(cancel)?; + let mut entry = entry.map_err(|e| io_err("reading archive", e))?; + if entry.header().entry_type().is_symlink() { + let path = entry.path().map_err(|e| io_err("reading archive path", e))?; + if let Some(target) = entry.link_name().map_err(|e| io_err("reading archive link", e))? { + validate_link(dest, &path, &target)?; + } + } + // `unpack_in` refuses escaping paths and validates link ancestors before writing. + entry.unpack_in(dest).map_err(|e| io_err("extracting", e))?; + } + Ok(()) + })(); + let sanitized = sanitize_tree(dest, dest); + result.and(sanitized) } fn extract_zip(archive: &Path, dest: &Path, cancel: &CancellationToken) -> IdeResult<()> { @@ -100,6 +112,7 @@ fn extract_zip(archive: &Path, dest: &Path, cancel: &CancellationToken) -> IdeRe if !is_safe_relative(&rel) { continue; } + reject_symlink_components(dest, &rel)?; let out = dest.join(&rel); if file.is_dir() { fs::create_dir_all(&out)?; @@ -112,6 +125,7 @@ fn extract_zip(archive: &Path, dest: &Path, cancel: &CancellationToken) -> IdeRe if file.is_symlink() { let mut target = String::new(); io::Read::read_to_string(&mut file, &mut target)?; + validate_link(dest, &rel, Path::new(&target))?; let _ = fs::remove_file(&out); std::os::unix::fs::symlink(target, &out)?; continue; @@ -132,14 +146,121 @@ fn extract_zip(archive: &Path, dest: &Path, cancel: &CancellationToken) -> IdeRe fn extract_with_tar(archive: &Path, dest: &Path, flags: &str, cancel: &CancellationToken) -> IdeResult<()> { let tar = which::which("tar").map_err(|_| IdeError::unavailable("`tar` is required to unpack this archive"))?; let mut cmd = std::process::Command::new(tar); - cmd.arg(flags).arg(archive).arg("-C").arg(dest); - let (ok, output, _) = crate::process::run_collect(cmd, cancel, &|_| {})?; + cmd.arg(flags).arg(archive).arg("--no-same-owner").arg("--no-same-permissions").arg("-C").arg(dest); + let result = crate::process::run_collect(cmd, cancel, &|_| {}); + // System tar implementations differ in which special permission bits they retain. + // Sanitize partial extractions too, without following archive-created links. + let sanitized = sanitize_tree(dest, dest); + let (ok, output, _) = result?; + sanitized?; if !ok { return Err(io_err("tar failed", output.trim())); } Ok(()) } +fn validate_single_name(dest: &Path, name: &str) -> IdeResult<()> { + let path = Path::new(name); + if !is_safe_relative(path) { + return Err(IdeError::invalid("archive filename escapes its destination")); + } + reject_symlink_components(dest, path) +} + +fn reject_symlink_components(dest: &Path, rel: &Path) -> IdeResult<()> { + let mut path = dest.to_path_buf(); + for component in rel.components() { + path.push(component); + match fs::symlink_metadata(&path) { + Ok(meta) if meta.file_type().is_symlink() => { + return Err(IdeError::invalid("archive entry traverses a symlink")); + } + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::NotFound => {} + Err(e) => return Err(e.into()), + } + } + Ok(()) +} + +fn validate_link(root: &Path, path: &Path, target: &Path) -> IdeResult<()> { + fn resolve(root: &Path, path: &Path, resolved: &mut PathBuf, links: &mut usize) -> IdeResult<()> { + for component in path.components() { + match component { + Component::Normal(name) => { + resolved.push(name); + match fs::symlink_metadata(root.join(&*resolved)) { + Ok(meta) if meta.file_type().is_symlink() => { + *links += 1; + if *links > 40 { + return Err(IdeError::invalid("archive symlink cycle")); + } + let target = fs::read_link(root.join(&*resolved))?; + resolved.pop(); + resolve(root, &target, resolved, links)?; + } + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::NotFound => {} + Err(e) => return Err(e.into()), + } + } + Component::CurDir => {} + Component::ParentDir if resolved.pop() => {} + _ => return Err(IdeError::invalid("archive symlink escapes its destination")), + } + } + Ok(()) + } + let mut resolved = PathBuf::new(); + let mut links = 0; + resolve(root, path.parent().unwrap_or(Path::new("")), &mut resolved, &mut links)?; + resolve(root, target, &mut resolved, &mut links) +} + +fn sanitize_tree(root: &Path, dir: &Path) -> IdeResult<()> { + #[cfg(unix)] + let mode = { + use std::os::unix::fs::PermissionsExt; + let mode = fs::metadata(dir)?.permissions().mode() & 0o777; + // Even a mode-000 directory may contain files whose special bits need clearing. + fs::set_permissions(dir, fs::Permissions::from_mode(mode | 0o700))?; + mode + }; + let mut error = None; + for entry in fs::read_dir(dir)? { + let result = (|| { + let path = entry?.path(); + let meta = fs::symlink_metadata(&path)?; + if meta.file_type().is_symlink() { + let target = fs::read_link(&path)?; + if let Err(e) = validate_link(root, path.strip_prefix(root).expect("entry below root"), &target) { + fs::remove_file(&path)?; + return Err(e); + } + } else { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&path, fs::Permissions::from_mode(meta.permissions().mode() & 0o777))?; + } + if meta.is_dir() { + sanitize_tree(root, &path)?; + } + } + Ok(()) + })(); + if let Err(e) = result { + error.get_or_insert(e); + } + } + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(dir, fs::Permissions::from_mode(mode))?; + } + error.map_or(Ok(()), Err) +} + fn is_safe_relative(p: &Path) -> bool { p.components().all(|c| matches!(c, Component::Normal(_) | Component::CurDir)) } @@ -149,7 +270,7 @@ pub fn make_executable(path: &Path) -> IdeResult<()> { { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(path)?.permissions(); - perms.set_mode(perms.mode() | 0o755); + perms.set_mode((perms.mode() & 0o777) | 0o755); fs::set_permissions(path, perms)?; } #[cfg(not(unix))] @@ -352,6 +473,203 @@ mod tests { assert!(dest.join("ok.txt").exists()); } + #[test] + fn zip_symlinks_are_confined_to_the_destination() { + for target in ["../outside", "/tmp/outside", "a/../../outside"] { + let tmp = tempfile::tempdir().unwrap(); + let archive = tmp.path().join("evil.zip"); + let mut z = zip::ZipWriter::new(File::create(&archive).unwrap()); + z.add_symlink("link", target, zip::write::SimpleFileOptions::default()).unwrap(); + z.finish().unwrap(); + let dest = tmp.path().join("out"); + assert!(extract(&archive, ArchiveKind::Zip, &dest, "x", &CancellationToken::new()).is_err()); + assert!(fs::symlink_metadata(dest.join("link")).is_err()); + } + // A link inside the archive (e.g. a framework's Versions/Current) still extracts. + let tmp = tempfile::tempdir().unwrap(); + let archive = tmp.path().join("ok.zip"); + let mut z = zip::ZipWriter::new(File::create(&archive).unwrap()); + z.start_file("Versions/A/lib", zip::write::SimpleFileOptions::default()).unwrap(); + io::Write::write_all(&mut z, b"x").unwrap(); + z.add_symlink("Versions/Current", "A", zip::write::SimpleFileOptions::default()).unwrap(); + z.finish().unwrap(); + let dest = tmp.path().join("out"); + extract(&archive, ArchiveKind::Zip, &dest, "x", &CancellationToken::new()).unwrap(); + assert_eq!(fs::read_link(dest.join("Versions/Current")).unwrap(), Path::new("A")); + assert_eq!(fs::read(dest.join("Versions/Current/lib")).unwrap(), b"x"); + } + + #[cfg(unix)] + #[test] + fn zip_does_not_write_or_chmod_through_existing_symlinks() { + use std::os::unix::fs::{PermissionsExt, symlink}; + for name in ["link", "dir/file"] { + let tmp = tempfile::tempdir().unwrap(); + let outside = tmp.path().join("outside"); + fs::create_dir(&outside).unwrap(); + let file = outside.join("file"); + fs::write(&file, "original").unwrap(); + fs::set_permissions(&file, fs::Permissions::from_mode(0o600)).unwrap(); + let dest = tmp.path().join("out"); + fs::create_dir(&dest).unwrap(); + symlink(&file, dest.join("link")).unwrap(); + symlink(&outside, dest.join("dir")).unwrap(); + let archive = tmp.path().join("evil.zip"); + zip_file(&archive, &[(name, b"replaced", 0o777)]); + assert!(extract(&archive, ArchiveKind::Zip, &dest, "x", &CancellationToken::new()).is_err()); + assert_eq!(fs::read_to_string(&file).unwrap(), "original"); + assert_eq!(fs::metadata(&file).unwrap().permissions().mode() & 0o777, 0o600); + } + } + + #[cfg(unix)] + #[test] + fn archives_strip_special_permission_bits() { + use std::os::unix::fs::PermissionsExt; + let tmp = tempfile::tempdir().unwrap(); + for kind in [ArchiveKind::TarGz, ArchiveKind::Zip, ArchiveKind::TarXz, ArchiveKind::TarBz2] { + let archive = tmp.path().join(format!("{kind:?}.archive")); + match kind { + ArchiveKind::TarGz => tar_gz(&archive, &[("tool", b"binary", 0o7777)]), + ArchiveKind::Zip => { + zip_file(&archive, &[("tool", b"binary", 0o777)]); + // ZipWriter strips special bits, so set the central directory's Unix mode directly. + let mut bytes = fs::read(&archive).unwrap(); + let central = bytes.windows(4).position(|w| w == b"PK\x01\x02").unwrap(); + bytes[central + 38..central + 42].copy_from_slice(&(0o107777_u32 << 16).to_le_bytes()); + fs::write(&archive, bytes).unwrap(); + } + _ => { + let source = tmp.path().join(format!("source-{kind:?}")); + fs::create_dir(&source).unwrap(); + fs::write(source.join("tool"), "binary").unwrap(); + fs::set_permissions(source.join("tool"), fs::Permissions::from_mode(0o7777)).unwrap(); + let flag = if kind == ArchiveKind::TarXz { "-cJf" } else { "-cjf" }; + assert!( + std::process::Command::new("tar") + .arg(flag) + .arg(&archive) + .arg("-C") + .arg(&source) + .arg("tool") + .status() + .unwrap() + .success() + ); + } + } + let dest = tmp.path().join(format!("out-{kind:?}")); + extract(&archive, kind, &dest, "x", &CancellationToken::new()).unwrap(); + let mode = fs::metadata(dest.join("tool")).unwrap().permissions().mode(); + assert_eq!(mode & 0o7000, 0, "{kind:?}"); + assert_ne!(mode & 0o111, 0, "{kind:?}"); + } + } + + #[test] + fn tar_entries_cannot_escape() { + for (name, link) in [("../escape", None), ("link", Some("../escape"))] { + let tmp = tempfile::tempdir().unwrap(); + let archive = tmp.path().join("evil.tar.gz"); + let gz = flate2::write::GzEncoder::new(File::create(&archive).unwrap(), flate2::Compression::fast()); + let mut builder = tar::Builder::new(gz); + let mut header = tar::Header::new_gnu(); + header.as_mut_bytes()[..name.len()].copy_from_slice(name.as_bytes()); + header.set_mode(0o7777); + header.set_size(0); + if let Some(target) = link { + header.set_entry_type(tar::EntryType::Symlink); + header.set_link_name(target).unwrap(); + } + header.set_cksum(); + builder.append(&header, io::empty()).unwrap(); + builder.into_inner().unwrap().finish().unwrap(); + let dest = tmp.path().join("out"); + let _ = extract(&archive, ArchiveKind::TarGz, &dest, "x", &CancellationToken::new()); + assert!(!tmp.path().join("escape").exists()); + assert!(fs::symlink_metadata(dest.join("link")).is_err()); + } + } + + #[cfg(unix)] + #[test] + fn compressed_tar_cannot_escape_or_retain_special_bits() { + use std::os::unix::fs::PermissionsExt; + // Both fixtures contain link -> ../outside, link/victim, ../escaped, and a mode-7777 file. + for (kind, bytes) in [ + (ArchiveKind::TarXz, include_bytes!("../tests/fixtures/escape.tar.xz").as_slice()), + (ArchiveKind::TarBz2, include_bytes!("../tests/fixtures/escape.tar.bz2").as_slice()), + ] { + let tmp = tempfile::tempdir().unwrap(); + fs::create_dir(tmp.path().join("outside")).unwrap(); + let victim = tmp.path().join("outside/victim"); + fs::write(&victim, "original").unwrap(); + fs::set_permissions(&victim, fs::Permissions::from_mode(0o600)).unwrap(); + let archive = tmp.path().join("evil.archive"); + fs::write(&archive, bytes).unwrap(); + let dest = tmp.path().join("out"); + assert!(extract(&archive, kind, &dest, "x", &CancellationToken::new()).is_err()); + assert_eq!(fs::read_to_string(&victim).unwrap(), "original"); + assert_eq!(fs::metadata(&victim).unwrap().permissions().mode() & 0o7777, 0o600); + assert!(!tmp.path().join("escaped").exists()); + assert!(fs::symlink_metadata(dest.join("link")).is_err()); + if let Ok(meta) = fs::metadata(dest.join("special")) { + assert_eq!(meta.permissions().mode() & 0o7000, 0); + } + } + } + + #[cfg(unix)] + #[test] + fn tar_link_resolution_handles_chains_and_dangling_targets() { + use std::os::unix::fs::symlink; + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + fs::create_dir(root.join("lib")).unwrap(); + symlink(".", root.join("alias")).unwrap(); + assert!(validate_link(root, Path::new("link"), Path::new("alias/../missing")).is_err()); + assert!(validate_link(root, Path::new("lib/link"), Path::new("../missing")).is_ok()); + symlink("cycle", root.join("cycle")).unwrap(); + assert!(validate_link(root, Path::new("link"), Path::new("cycle")).is_err()); + } + + #[cfg(unix)] + #[test] + fn sanitizing_clears_all_modes_even_after_bad_links() { + use std::os::unix::fs::{PermissionsExt, symlink}; + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + for name in ["a", "b"] { + let dir = root.join(name); + fs::create_dir(&dir).unwrap(); + symlink("../../outside", dir.join("bad-link")).unwrap(); + fs::write(dir.join("tool"), "binary").unwrap(); + fs::set_permissions(dir.join("tool"), fs::Permissions::from_mode(0o7777)).unwrap(); + fs::set_permissions(&dir, fs::Permissions::from_mode(0o7000)).unwrap(); + } + fs::set_permissions(root, fs::Permissions::from_mode(0o7777)).unwrap(); + assert!(sanitize_tree(root, root).is_err()); + assert_eq!(fs::metadata(root).unwrap().permissions().mode() & 0o7000, 0); + for name in ["a", "b"] { + let dir = root.join(name); + assert_eq!(fs::metadata(&dir).unwrap().permissions().mode() & 0o7777, 0); + fs::set_permissions(&dir, fs::Permissions::from_mode(0o700)).unwrap(); + assert!(fs::symlink_metadata(dir.join("bad-link")).is_err()); + assert_eq!(fs::metadata(dir.join("tool")).unwrap().permissions().mode() & 0o7000, 0); + } + } + + #[test] + fn single_payload_names_cannot_escape() { + let tmp = tempfile::tempdir().unwrap(); + let archive = tmp.path().join("raw"); + fs::write(&archive, "payload").unwrap(); + for kind in [ArchiveKind::Raw, ArchiveKind::Gz] { + assert!(extract(&archive, kind, &tmp.path().join("out"), "../escaped", &CancellationToken::new()).is_err()); + assert!(!tmp.path().join("escaped").exists()); + } + } + #[test] fn single_gzip_and_raw_payloads() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/ide-tools/tests/fixtures/escape.tar.bz2 b/crates/ide-tools/tests/fixtures/escape.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..905e883cfadffedcf87be02d3c1f3f26ef5784fb GIT binary patch literal 189 zcmV;u07CylT4*^jL0KkKS?Dh0_5c9Tdw|P;AOJuC|9}G|ZZF>e2M90#wrFZ<8W@@Y z01X;42`WYa003ziBLb=sJwro4&>0N?(c3Y}jG0IeCq{%P9}-2N(h@1Mv)D)jggT(4 zgc1nmJQ6Uf!%X47AVU~(w%ezPGAn4J5aSiqYn2H~b<;`BB!o<(ee2%Ap__w|i*}kq r&5npUDwui}64WfpK#Wd69srlZ@?~jY|ig2MJ&|S&w?hQ-e literal 0 HcmV?d00001 diff --git a/crates/ide-tools/tests/fixtures/escape.tar.xz b/crates/ide-tools/tests/fixtures/escape.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..36a20199246375f58d2c28d41bcfb74405836376 GIT binary patch literal 228 zcmVv&3N*|H65ZtqdZ>{8G zpE&^jtLm)F>ZK%(kSpYdo%GRh`#M7;zcXqXW-p7+qG8PkQLp;#_rHg}GQe(JFx5eI z`fZivU6)M21g}_ literal 0 HcmV?d00001 diff --git a/crates/ide-tools/tests/fixtures/generate_escape.py b/crates/ide-tools/tests/fixtures/generate_escape.py new file mode 100644 index 00000000..e8c548b5 --- /dev/null +++ b/crates/ide-tools/tests/fixtures/generate_escape.py @@ -0,0 +1,27 @@ +"""Regenerate the malicious tar fixtures used by archive.rs tests (Python stdlib only).""" + +import bz2 +import io +import lzma +from pathlib import Path +import tarfile + +archive = io.BytesIO() +with tarfile.open(fileobj=archive, mode="w", format=tarfile.GNU_FORMAT) as tar: + for name, target, data in [ + ("link", "../outside", b""), + ("link/victim", None, b"replaced"), + ("../escaped", None, b"escaped"), + ("special", None, b"special"), + ]: + entry = tarfile.TarInfo(name) + entry.mode = 0o7777 + if target: + entry.type = tarfile.SYMTYPE + entry.linkname = target + else: + entry.size = len(data) + tar.addfile(entry, io.BytesIO(data)) + +for extension, compress in [("xz", lzma.compress), ("bz2", bz2.compress)]: + Path(__file__).with_name(f"escape.tar.{extension}").write_bytes(compress(archive.getvalue())) diff --git a/crates/ide-vfs/Cargo.toml b/crates/ide-vfs/Cargo.toml index d99de38f..c40df9cc 100644 --- a/crates/ide-vfs/Cargo.toml +++ b/crates/ide-vfs/Cargo.toml @@ -19,7 +19,7 @@ chardetng.workspace = true trash.workspace = true tracing.workspace = true parking_lot.workspace = true -zip = { version = "=2.4.2", default-features = false, features = ["deflate"] } +zip.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/ide-vfs/src/archive.rs b/crates/ide-vfs/src/archive.rs index f6915539..84c2cb70 100644 --- a/crates/ide-vfs/src/archive.rs +++ b/crates/ide-vfs/src/archive.rs @@ -120,7 +120,8 @@ impl Archive { nodes.insert(String::new(), Node { dir: true, size: 0, index: None, children: Vec::new() }); for i in 0..zip.len() { let Ok(file) = zip.by_index_raw(i) else { continue }; - let Some(name) = clean_name(file.name()) else { continue }; + let Ok(name) = file.name() else { continue }; + let Some(name) = clean_name(&name) else { continue }; let (dir, size) = (file.is_dir(), file.size()); drop(file); let mut parent = parent_of(&name).0; diff --git a/crates/ide-workspace/Cargo.toml b/crates/ide-workspace/Cargo.toml index b963c5e3..cfb75f56 100644 --- a/crates/ide-workspace/Cargo.toml +++ b/crates/ide-workspace/Cargo.toml @@ -23,4 +23,4 @@ globset.workspace = true [dev-dependencies] tempfile.workspace = true encoding_rs.workspace = true -zip = { version = "=2.4.2", default-features = false, features = ["deflate"] } +zip.workspace = true diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..418b97b3 --- /dev/null +++ b/deny.toml @@ -0,0 +1,26 @@ +[advisories] +version = 2 +unmaintained = "workspace" +yanked = "warn" +ignore = [ + # SQLx MySQL uses RSA public-key encryption; russh/ssh-key use signatures. + # No RSA private-key decryption is exposed to attacker-controlled ciphertext. + { id = "RUSTSEC-2023-0071", reason = "RSA is used for MySQL password encryption and SSH signatures, not RSA decryption" }, +] + +[licenses] +allow = [ + "MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause", + "BSD-3-Clause", "ISC", "Zlib", "0BSD", "CC0-1.0", "Unlicense", + # File-level copyleft, used unmodified as dependencies. + "MPL-2.0", + "Unicode-3.0", "Unicode-DFS-2016", "CDLA-Permissive-2.0", "OpenSSL", +] + +[bans] +multiple-versions = "warn" + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"]