Merge pull request #4234 from kumpera/katia-flavia
authorRodrigo Kumpera <kumpera@users.noreply.github.com>
Wed, 18 Jan 2017 06:21:30 +0000 (01:21 -0500)
committerGitHub <noreply@github.com>
Wed, 18 Jan 2017 06:21:30 +0000 (01:21 -0500)
[runtime] Deny loading bad images.

mono/metadata/image.c
tools/nuget-hash-extractor/Makefile [new file with mode: 0644]
tools/nuget-hash-extractor/download.sh [new file with mode: 0755]
tools/nuget-hash-extractor/nuget-hash-extractor.cs [new file with mode: 0644]

index 4293480be55382ae0071d0c8323975c7bbfac4a7..4b4f06beda9cd38194238baeebe296663a91951b 100644 (file)
@@ -1077,6 +1077,102 @@ install_pe_loader (void)
        mono_install_image_loader (&pe_loader);
 }
 
+/*
+Ignored assemblies.
+
+There are some assemblies we need to ignore because they include an implementation that doesn't work under mono.
+Mono provides its own implementation of those assemblies so it's safe to do so.
+
+The ignored_assemblies list is generated using tools/nuget-hash-extractor and feeding the problematic nugets to it.
+
+Right now the list of nugets are the ones that provide the assemblies in $ignored_assemblies_names.
+
+This is to be removed once a proper fix is shipped through nuget.
+
+*/
+
+typedef enum {
+       SYS_RT_INTEROP_RUNTIME_INFO = 0, //System.Runtime.InteropServices.RuntimeInformation
+       SYS_GLOBALIZATION_EXT = 1, //System.Globalization.Extensions
+       SYS_IO_COMPRESSION = 2, //System.IO.Compression
+       SYS_NET_HTTP = 3, //System.Net.Http
+       SYS_TEXT_ENC_CODEPAGES = 4, //System.Text.Encoding.CodePages
+} IgnoredAssemblyNames;
+
+typedef struct {
+       int hash;
+       int assembly_name;
+       const char guid [40];
+} IgnoredAssembly;
+
+const char *ignored_assemblies_names[] = {
+       "System.Runtime.InteropServices.RuntimeInformation.dll",
+       "System.Globalization.Extensions.dll",
+       "System.IO.Compression.dll",
+       "System.Net.Http.dll",
+       "System.Text.Encoding.CodePages.dll"
+};
+
+#define IGNORED_ASSEMBLY(HASH, NAME, GUID, VER_STR)    { .hash = HASH, .assembly_name = NAME, .guid = GUID }
+
+static const IgnoredAssembly ignored_assemblies [] = {
+       IGNORED_ASSEMBLY (0x1136045D, SYS_GLOBALIZATION_EXT, "475DBF02-9F68-44F1-8FB5-C9F69F1BD2B1", "4.0.0 net46"),
+       IGNORED_ASSEMBLY (0x358C9723, SYS_GLOBALIZATION_EXT, "5FCD54F0-4B97-4259-875D-30E481F02EA2", "4.0.1 net46"),
+       IGNORED_ASSEMBLY (0x450A096A, SYS_GLOBALIZATION_EXT, "E9FCFF5B-4DE1-4BDC-9CE8-08C640FC78CC", "4.3.0 net46"),
+       IGNORED_ASSEMBLY (0x7A39EA2D, SYS_IO_COMPRESSION, "C665DC9B-D9E5-4D00-98ED-E4F812F23545", "4.0.0 netcore50"),
+       IGNORED_ASSEMBLY (0x1CBD59A2, SYS_IO_COMPRESSION, "44FCA06C-A510-4B3E-BDBF-D08D697EF65A", "4.1.0 net46"),
+       IGNORED_ASSEMBLY (0x5E393C29, SYS_IO_COMPRESSION, "3A58A219-266B-47C3-8BE8-4E4F394147AB", "4.3.0 net46"),
+       IGNORED_ASSEMBLY (0x726C7CC1, SYS_NET_HTTP, "7C0B577F-A4FD-47F1-ADF5-EE65B5A04BB5", "4.0.0 netcore50"),
+       IGNORED_ASSEMBLY (0x27726A90, SYS_NET_HTTP, "269B562C-CC15-4736-B1B1-68D4A43CAA98", "4.1.0 net46"),
+       IGNORED_ASSEMBLY (0x10CADA75, SYS_NET_HTTP, "EA2EC6DC-51DD-479C-BFC2-E713FB9E7E47", "4.1.1 net46"),
+       IGNORED_ASSEMBLY (0x8437178B, SYS_NET_HTTP, "C0E04D9C-70CF-48A6-A179-FBFD8CE69FD0", "4.3.0 net46"),
+       IGNORED_ASSEMBLY (0x46A4A1C5, SYS_RT_INTEROP_RUNTIME_INFO, "F13660F8-9D0D-419F-BA4E-315693DD26EA", "4.0.0 net45"),
+       IGNORED_ASSEMBLY (0xD07383BB, SYS_RT_INTEROP_RUNTIME_INFO, "DD91439F-3167-478E-BD2C-BF9C036A1395", "4.3.0 net45"),
+       IGNORED_ASSEMBLY (0x911D9EC3, SYS_TEXT_ENC_CODEPAGES, "C142254F-DEB5-46A7-AE43-6F10320D1D1F", "4.0.1 net46"),
+       IGNORED_ASSEMBLY (0xFA686A38, SYS_TEXT_ENC_CODEPAGES, "FD178CD4-EF4F-44D5-9C3F-812B1E25126B", "4.3.0 net46"),
+};
+
+/*
+Equivalent C# code:
+       static void Main  () {
+               string str = "...";
+               int h = 5381;
+        for (int i = 0;  i < str.Length; ++i)
+            h = ((h << 5) + h) ^ str[i];
+
+               Console.WriteLine ("{0:X}", h);
+       }
+*/
+static int
+hash_guid (const char *str)
+{
+       int h = 5381;
+    while (*str) {
+        h = ((h << 5) + h) ^ *str;
+               ++str;
+       }
+
+       return h;
+}
+
+static gboolean
+is_problematic_image (MonoImage *image)
+{
+       int h = hash_guid (image->guid);
+
+       //TODO make this more cache effiecient.
+       // Either sort by hash and bseach or use SoA and make the linear search more cache efficient.
+       for (int i = 0; i < G_N_ELEMENTS (ignored_assemblies); ++i) {
+               if (ignored_assemblies [i].hash == h && !strcmp (image->guid, ignored_assemblies [i].guid)) {
+                       const char *needle = ignored_assemblies_names [ignored_assemblies [i].assembly_name];
+                       char *p = strcasestr (image->name, needle);
+                       if (p && p [strlen (needle)] == 0)
+                               return TRUE;
+               }
+       }
+       return FALSE;
+}
+
 static MonoImage *
 do_mono_image_load (MonoImage *image, MonoImageOpenStatus *status,
                    gboolean care_about_cli, gboolean care_about_pecoff)
@@ -1132,6 +1228,12 @@ do_mono_image_load (MonoImage *image, MonoImageOpenStatus *status,
        if (!mono_image_load_cli_data (image))
                goto invalid_image;
 
+       if (is_problematic_image (image)) {
+               mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_ASSEMBLY, "Denying load of problematic image %s", image->name);
+               *status = MONO_IMAGE_IMAGE_INVALID;
+               goto invalid_image;
+       }
+
        if (image->loader == &pe_loader && !image->metadata_only && !mono_verifier_verify_table_data (image, &errors))
                goto invalid_image;
 
diff --git a/tools/nuget-hash-extractor/Makefile b/tools/nuget-hash-extractor/Makefile
new file mode 100644 (file)
index 0000000..1d52b77
--- /dev/null
@@ -0,0 +1,13 @@
+SOURCES = \
+       nuget-hash-extractor.cs
+
+nuget-hash-extractor.exe: $(SOURCES)
+        mcs /r:System.Xml.Linq  /r:System.IO.Compression -o:nuget-hash-extractor.exe $(SOURCES)
+
+download:
+       echo "Downloading all the nugets";      \
+       ./download.sh
+
+run: nuget-hash-extractor.exe
+       mono nuget-hash-extractor.exe nugets
+.PHONY: download run
diff --git a/tools/nuget-hash-extractor/download.sh b/tools/nuget-hash-extractor/download.sh
new file mode 100755 (executable)
index 0000000..978ce89
--- /dev/null
@@ -0,0 +1,27 @@
+mkdir nugets
+
+#System.Runtime.InteropServices.RuntimeInformation
+wget https://www.nuget.org/api/v2/package/System.Runtime.InteropServices.RuntimeInformation/4.3.0 -O nugets/system.runtime.interopservices.runtimeinformation.4.3.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.Runtime.InteropServices.RuntimeInformation/4.0.0 -O nugets/system.runtime.interopservices.runtimeinformation.4.0.0.nupkg
+
+#System.Globalization.Extensions
+wget https://www.nuget.org/api/v2/package/System.Globalization.Extensions/4.3.0 -O nugets/system.globalization.extensions.4.3.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.Globalization.Extensions/4.0.1 -O nugets/system.globalization.extensions.4.0.1.nupkg
+wget https://www.nuget.org/api/v2/package/System.Globalization.Extensions/4.0.0 -O nugets/system.globalization.extensions.4.0.0.nupkg
+
+#System.IO.Compression
+wget https://www.nuget.org/api/v2/package/System.IO.Compression/4.3.0 -O nugets/system.io.compression.4.3.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.IO.Compression/4.1.0 -O nugets/system.io.compression.4.1.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.IO.Compression/4.0.0 -O nugets/system.io.compression.4.0.0.nupkg
+
+#System.Net.Http
+wget https://www.nuget.org/api/v2/package/System.Net.Http/4.3.0 -O nugets/system.net.http.4.3.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.Net.Http/4.1.1 -O nugets/system.net.http.4.1.1.nupkg
+wget https://www.nuget.org/api/v2/package/System.Net.Http/4.1.0 -O nugets/system.net.http.4.1.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.Net.Http/4.0.0 -O nugets/system.net.http.4.0.0.nupkg
+
+#System.Text.Encoding.CodePages
+wget https://www.nuget.org/api/v2/package/System.Text.Encoding.CodePages/4.3.0 -O nugets/system.text.encoding.codepages.4.3.0.nupkg
+wget https://www.nuget.org/api/v2/package/System.Text.Encoding.CodePages/4.0.1 -O nugets/system.text.encoding.codepages.4.0.1.nupkg
+wget https://www.nuget.org/api/v2/package/System.Text.Encoding.CodePages/4.0.0 -O nugets/system.text.encoding.codepages.4.0.0.nupkg
+
diff --git a/tools/nuget-hash-extractor/nuget-hash-extractor.cs b/tools/nuget-hash-extractor/nuget-hash-extractor.cs
new file mode 100644 (file)
index 0000000..310abd2
--- /dev/null
@@ -0,0 +1,85 @@
+using System;
+using System.Xml.Linq;
+using System.Linq;
+using System.IO;
+using System.IO.Compression;
+using System.Reflection;
+
+class Driver {
+       static ZipArchiveEntry FindSpecFile (ZipArchive zip) {
+               foreach (var entry in zip.Entries) {
+                       if (entry.Name.EndsWith (".nuspec"))
+                               return entry;
+               }
+               throw new Exception ("Could not find nuspec file");
+       }
+
+       static void DumpNuget (string nupkg) {
+               var zip = new ZipArchive(new FileStream (nupkg, FileMode.Open));
+
+               var nuspec = FindSpecFile (zip);
+               var l = XElement.Load (new StreamReader (nuspec.Open ()));
+               var version = (from el in l.Descendants() where el.Name.LocalName == "version" select el.Value).FirstOrDefault ();
+
+               foreach (var et in from e in zip.Entries where (e.FullName.StartsWith ("lib/net4") || e.FullName.StartsWith ("lib/netcore")) && e.Name.EndsWith (".dll") select e) {
+                       LoadAndDump (et, version);
+               }
+       }
+       static void Main (string[] args) {
+               foreach (var f in Directory.GetFiles (args [0], "*.nupkg")) {
+                       DumpNuget (f);
+               }
+       }
+
+       static byte[] StreamToArray (Stream s) {
+               using(var ms = new MemoryStream ()) {
+                       s.CopyTo (ms);
+                       return ms.ToArray ();
+               }
+       }
+
+       static int domain_id = 1;
+       static void LoadAndDump (ZipArchiveEntry entry, string version) {
+               // Console.WriteLine ("Dumping {0}", entry);
+               var data = StreamToArray (entry.Open ());
+               AppDomain ad = AppDomain.CreateDomain ("parse_" + ++domain_id);
+               DoParse p = (DoParse)ad.CreateInstanceAndUnwrap (typeof (DoParse).Assembly.FullName, typeof (DoParse).FullName);
+               p.ParseAssembly (data, version, entry.Name, entry.FullName);
+               AppDomain.Unload (ad);
+       }
+}
+
+class DoParse : MarshalByRefObject {
+       static int Hash (string str) {
+               int h = 5381;
+        for (int i = 0;  i < str.Length; ++i)
+            h = ((h << 5) + h) ^ str[i];
+               return h;
+       }
+       static string FileToEnum (string name) {
+               switch (name) {
+               case "System.Runtime.InteropServices.RuntimeInformation.dll": return "SYS_RT_INTEROP_RUNTIME_INFO";
+               case "System.Globalization.Extensions.dll": return "SYS_GLOBALIZATION_EXT";
+               case "System.IO.Compression.dll": return "SYS_IO_COMPRESSION";
+               case "System.Net.Http.dll": return "SYS_NET_HTTP";
+               case "System.Text.Encoding.CodePages.dll": return "SYS_TEXT_ENC_CODEPAGES";
+               default: throw new Exception ($"No idea what to do with {name}");
+               }
+       }
+
+       static string FileToMoniker (string p) {
+               var parts = p.Split (Path.DirectorySeparatorChar);
+               return parts[parts.Length - 2];
+       }
+
+       public void ParseAssembly (byte[] data, string version, string name, string fullname) {
+               var a = Assembly.ReflectionOnlyLoad (data);
+               var m = a.GetModules ()[0];
+               var id = m.ModuleVersionId.ToString ().ToUpper ();
+               var hash_code = Hash (id).ToString ("X");
+               var str = FileToEnum (name);
+
+               string ver_str = version + " " + FileToMoniker (fullname);      
+               Console.WriteLine ($"IGNORED_ASSEMBLY (0x{hash_code}, {str}, \"{id}\", \"{ver_str}\"),");
+       }
+}
\ No newline at end of file