mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 02:06:05 -10:00
fix(deps): upgrade crates with security advisories (#338)
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 上验证解压。
This commit is contained in:
1 parent
b1be3c6a09
commit
7da58637c2
15 files changed
+584
-224
No files matched your search
Generated
+132
-176
@@ -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"
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+60
-28
@@ -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<bb8_tiberius::rt::Client>);
|
||||
type MssqlClient = tiberius::Client<tokio_util::compat::Compat<tokio::net::TcpStream>>;
|
||||
|
||||
pub struct MssqlConnection(Option<MssqlClient>);
|
||||
|
||||
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::Connection, Self::Error> {
|
||||
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<bb8_tiberius::Error>) -> 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<bb8_tiberius::Error>) -> IdeError {
|
||||
fn mssql_pool_err(e: bb8::RunError<tiberius::error::Error>) -> 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<ResultColumn>, Vec<Vec<Option<String>>>, Option<u64>, bool, Option<Instant>);
|
||||
|
||||
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<Fetched> {
|
||||
async fn mssql_fetch(conn: &mut MssqlClient, sql: &str, max: usize) -> IdeResult<Fetched> {
|
||||
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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
+329
-11
@@ -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<R: io::Read>(mut archive: tar::Archive<R>, 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();
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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()))
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
Reference in new issue
Block a user