diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..0089e35e4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + "src/CargoCLock/Cargo.toml" + ] +} \ No newline at end of file diff --git a/global.json b/global.json index a99f94e05..aea164061 100644 --- a/global.json +++ b/global.json @@ -2,5 +2,8 @@ "sdk": { "version": "8.0.408", "rollForward": "latestMinor" + }, + "msbuild-sdks": { + "Microsoft.Build.Cargo": "1.0.350-preview" } } diff --git a/src/CargoCLock/.gitignore b/src/CargoCLock/.gitignore new file mode 100644 index 000000000..08c4841f0 --- /dev/null +++ b/src/CargoCLock/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +target/ \ No newline at end of file diff --git a/src/CargoCLock/Cargo.lock b/src/CargoCLock/Cargo.lock new file mode 100644 index 000000000..14b047d7a --- /dev/null +++ b/src/CargoCLock/Cargo.lock @@ -0,0 +1,497 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cargo-lock" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06acb4f71407ba205a07cb453211e0e6a67b21904e47f6ba1f9589e38f2e454" +dependencies = [ + "petgraph", + "semver", + "serde", + "toml", + "url", +] + +[[package]] +name = "cargo_c_lock" +version = "0.1.0" +dependencies = [ + "cargo-lock", + "serde_json", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/CargoCLock/Cargo.toml b/src/CargoCLock/Cargo.toml new file mode 100644 index 000000000..c99183550 --- /dev/null +++ b/src/CargoCLock/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cargo_c_lock" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cargo-lock = { version = "10.1.0", features = ["dependency-tree"] } +serde_json = "1.0.140" diff --git a/src/CargoCLock/CargoCLock.proj b/src/CargoCLock/CargoCLock.proj new file mode 100644 index 000000000..30319636a --- /dev/null +++ b/src/CargoCLock/CargoCLock.proj @@ -0,0 +1,3 @@ + + + diff --git a/src/CargoCLock/src/lib.rs b/src/CargoCLock/src/lib.rs new file mode 100644 index 000000000..5ad09750b --- /dev/null +++ b/src/CargoCLock/src/lib.rs @@ -0,0 +1,50 @@ +use cargo_lock::Lockfile; +use std::{ + ffi::{CStr, CString}, + path::PathBuf, +}; + +// SAFETY: there is no other global function of this name +#[unsafe(no_mangle)] +pub extern "C" fn json(path_ptr: *const i8) -> *mut i8 { + // SAFETY: The caller must guarantee that path_ptr is a valid null-terminated C string. + let path_cstr = unsafe { CStr::from_ptr(path_ptr) }; + let path: PathBuf = path_cstr.to_str().expect("Invalid UTF-8 in path").into(); + + let lockfile = Lockfile::load(path).unwrap(); + let serialized = serde_json::to_string(&lockfile.packages).unwrap(); + + let json_cstring = CString::new(serialized).unwrap(); + let json_ptr = json_cstring.into_raw(); + json_ptr +} + +// SAFETY: there is no other global function of this name +#[unsafe(no_mangle)] +pub extern "C" fn free(json_ptr: *mut i8) { + if json_ptr.is_null() { + return; + } + + // SAFETY: for manual deallocation. + unsafe { drop(CString::from_raw(json_ptr)) }; +} + +#[test] +fn test_json() { + // test-only: create the path argument, which would be caller-owned. + let path = std::env::current_dir().unwrap().join("Cargo.lock"); + let path_cstring = CString::new(path.to_str().unwrap()).unwrap(); + let path_ptr = path_cstring.as_ptr(); + + // Get a Rust-owned pointer to the JSON string. + let json_ptr = json(path_ptr); + + // Get a reference to the contents so we can test the `free` call: + let json_cstr = unsafe { CStr::from_ptr(json_ptr) }; + let json_str = json_cstr.to_str().unwrap(); + assert!(json_str.contains("name")); + + // Let Rust know it can clean up. + free(json_ptr); +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0eb245117..1c4e4924e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,6 +3,7 @@ + true true snupkg diff --git a/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj b/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj index a0fb3f5e4..ec31adcd7 100644 --- a/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj +++ b/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj @@ -20,7 +20,8 @@ - + + @@ -33,4 +34,10 @@ + + + Always + + + diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/LockfileLoader.cs b/src/Microsoft.ComponentDetection.Detectors/rust/LockfileLoader.cs new file mode 100644 index 000000000..0fb9852fe --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/rust/LockfileLoader.cs @@ -0,0 +1,84 @@ +namespace Microsoft.ComponentDetection.Detectors.Rust; + +#pragma warning disable IDE0007 // Use implicit type +#pragma warning disable SA1402 // File may only contain a single type + +using System; +using System.Runtime.InteropServices; +using System.Text; + +public sealed class LockfileLoader : IDisposable +{ + private readonly LockfileHandle lockfile; + private string lockfileString; + + public LockfileLoader(string lockfilePath) => this.lockfile = Native.Json(lockfilePath); + + public override string ToString() + { + this.lockfileString ??= this.lockfile.AsString(); + return this.lockfileString; + } + + public void Dispose() + { + this.lockfile.Dispose(); + } +} + +/// +/// Provides interop methods for working with the Rust-produced native cargo_c_lock library. +/// +internal partial class Native +{ + [LibraryImport("cargo_c_lock", EntryPoint = "json", StringMarshalling = StringMarshalling.Utf8)] + [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] + internal static partial LockfileHandle Json(string lockfilePath); + + /// + /// Frees the memory associated with the specified JSON handle. + /// + /// The handle to the JSON memory to free. + [LibraryImport("cargo_c_lock", EntryPoint = "free")] + [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] + internal static partial void Free(IntPtr jsonHandle); +} + +internal class LockfileHandle : SafeHandle +{ + public LockfileHandle() + : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid + { + get { return this.handle == IntPtr.Zero; } + } + + public string AsString() + { + int len = 0; + while (Marshal.ReadByte(this.handle, len) != 0) + { + ++len; + } + + byte[] buffer = new byte[len]; + Marshal.Copy(this.handle, buffer, 0, buffer.Length); + return Encoding.UTF8.GetString(buffer); + } + + protected override bool ReleaseHandle() + { + if (!this.IsInvalid) + { + Native.Free(this.handle); + } + + return true; + } +} + +#pragma warning restore SA1402 // File may only contain a single type +#pragma warning restore IDE0007 // Use implicit type diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/RustSbomDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/RustSbomDetectorTests.cs index 7e01e2cf6..63d72a4bc 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/RustSbomDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/RustSbomDetectorTests.cs @@ -206,6 +206,8 @@ public class RustSbomDetectorTests : BaseDetectorTest public async Task TestGraphIsCorrectAsync() { var sbom = CargoSbom.FromJson(this.testSbom); + var lockfile = new LockfileLoader("C:\\Users\\nahammond\\source\\repos\\component-detection\\src\\CargoCLock\\Cargo.lock"); + var mystring = lockfile.ToString(); var (result, componentRecorder) = await this.DetectorTestUtility .WithFile("main.exe.cargo-sbom.json", this.testSbom)