提交 d01e3a2f 编写于 作者: P pgavlin

Validate public keys from metadata.

This is necessary to correctly reject invalid assembly references and to match the native compiler. (changeset 1396814)
上级 318b2b0e
......@@ -24,8 +24,7 @@ public void Equality()
var id4 = new AssemblyIdentity("Foo", new Version(1, 0, 1, 0), "", RoPublicKeyToken1, hasPublicKey: false, isRetargetable: false);
var id5 = new AssemblyIdentity("Foo", new Version(1, 0, 0, 0), "en-US", RoPublicKeyToken1, hasPublicKey: false, isRetargetable: false);
var id6 = new AssemblyIdentity("Foo", new Version(1, 0, 0, 0), "", default(ImmutableArray<byte>), hasPublicKey: false, isRetargetable: false);
var id7 = new AssemblyIdentity("Foo", new Version(1, 0, 0, 0), "", RoPublicKeyToken1, hasPublicKey: true, isRetargetable: false);
var id8 = new AssemblyIdentity("Foo", new Version(1, 0, 0, 0), "", RoPublicKey1, hasPublicKey: true, isRetargetable: true);
var id7 = new AssemblyIdentity("Foo", new Version(1, 0, 0, 0), "", RoPublicKey1, hasPublicKey: true, isRetargetable: true);
var win1 = new AssemblyIdentity("Foo", new Version(1, 0, 0, 0), "", RoPublicKey1, hasPublicKey: true, isRetargetable: false, contentType: AssemblyContentType.WindowsRuntime);
var win2 = new AssemblyIdentity("Bar", new Version(1, 0, 0, 0), "", RoPublicKey1, hasPublicKey: true, isRetargetable: false, contentType: AssemblyContentType.WindowsRuntime);
......@@ -44,7 +43,6 @@ public void Equality()
Assert.False(id1.Equals(id5));
Assert.False(id1.Equals(id6));
Assert.False(id1.Equals(id7));
Assert.False(id1.Equals(id8));
Assert.Equal((object)id1, id1);
Assert.NotNull(id1);
......@@ -233,24 +231,6 @@ public void MetadataConstructor()
AssertEx.Equal(PublicKeyToken1, id.PublicKeyToken);
Assert.Equal(AssemblyContentType.Default, id.ContentType);
// incorrect size of the key token:
id = new AssemblyIdentity("Foo", new Version(1, 2, 3, 4), null, RoPublicKey1, hasPublicKey: false, isRetargetable: false, contentType: AssemblyContentType.Default, noThrow: true);
Assert.Equal(AssemblyNameFlags.PublicKey, id.Flags);
Assert.Equal("", id.CultureName);
Assert.Equal(true, id.HasPublicKey);
AssertEx.Equal(PublicKey1, id.PublicKey);
AssertEx.Equal(PublicKeyToken1, id.PublicKeyToken);
Assert.Equal(AssemblyContentType.Default, id.ContentType);
// missing key:
id = new AssemblyIdentity("Foo", new Version(1, 2, 3, 4), null, ImmutableArray.Create<byte>(), hasPublicKey: true, isRetargetable: false, contentType: AssemblyContentType.Default, noThrow: true);
Assert.Equal(AssemblyNameFlags.None, id.Flags);
Assert.Equal("", id.CultureName);
Assert.Equal(false, id.HasPublicKey);
Assert.Equal(0, id.PublicKey.Length);
Assert.Equal(0, id.PublicKeyToken.Length);
Assert.Equal(AssemblyContentType.Default, id.ContentType);
// invalid content type:
id = new AssemblyIdentity("Foo", new Version(1, 2, 3, 4), null, ImmutableArray.Create<byte>(), hasPublicKey: false, isRetargetable: false, contentType: (AssemblyContentType)2, noThrow: true);
Assert.Equal(AssemblyNameFlags.None, id.Flags);
......@@ -277,15 +257,6 @@ public void MetadataConstructor()
Assert.Equal("blah,", id.CultureName);
id = new AssemblyIdentity("Foo", new Version(1, 2, 3, 4), "*", ImmutableArray.Create<byte>(), hasPublicKey: false, isRetargetable: false, contentType: AssemblyContentType.Default, noThrow: true);
Assert.Equal("*", id.CultureName);
// public key is default (mimics PEModule.CreateAssemblyIdentityOrThrow in case where "publicKey.IsNil")
id = new AssemblyIdentity("Foo", new Version(1, 2, 3, 4), null, default(ImmutableArray<byte>), hasPublicKey: true, isRetargetable: false, contentType: AssemblyContentType.Default, noThrow: true);
Assert.Equal(AssemblyNameFlags.None, id.Flags);
Assert.Equal("", id.CultureName);
Assert.Equal(false, id.HasPublicKey);
Assert.Equal(0, id.PublicKey.Length);
Assert.Equal(0, id.PublicKeyToken.Length);
Assert.Equal(AssemblyContentType.Default, id.ContentType);
}
[Fact]
......
......@@ -471,5 +471,14 @@ public void DocCommentProvider()
var summary = list.GetDocumentationCommentXml();
Assert.Equal("<member name='T:System.Collections.ArrayList'><summary>T:System.Collections.ArrayList</summary></member>", summary);
}
[Fact]
public void InvalidPublicKey()
{
var r = MetadataReference.CreateFromStream(new MemoryStream(TestResources.SymbolsTests.Metadata.InvalidPublicKey, writable: false));
Assert.Equal(CodeAnalysisResources.InMemoryAssembly, r.Display);
Assert.Throws<BadImageFormatException>(((AssemblyMetadata)r.GetMetadata()).GetAssembly);
}
}
}
......@@ -969,5 +969,23 @@ internal class CodeAnalysisResources {
return ResourceManager.GetString("XmlReferencesNotSupported", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Invalid public key..
/// </summary>
internal static string InvalidPublicKey {
get {
return ResourceManager.GetString("InvalidPublicKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Invalid public key token..
/// </summary>
internal static string InvalidPublicKeyToken {
get {
return ResourceManager.GetString("InvalidPublicKeyToken", resourceCulture);
}
}
}
}
......@@ -420,4 +420,10 @@
<data name="MissingListItem" xml:space="preserve">
<value>The item specified is not the element of a list.</value>
</data>
</root>
\ No newline at end of file
<data name="InvalidPublicKey" xml:space="preserve">
<value>Invalid public key.</value>
</data>
<data name="InvalidPublicKeyToken" xml:space="preserve">
<value>Invalid public key token.</value>
</data>
</root>
......@@ -897,5 +897,139 @@ internal static bool SplitNameEqualsFullyQualifiedName(string namespaceName, str
fullyQualified.StartsWith(namespaceName, StringComparison.Ordinal) &&
fullyQualified.EndsWith(typeName, StringComparison.Ordinal);
}
internal static bool IsValidPublicKey(ImmutableArray<byte> bytes)
{
return PublicKeyDecoder.TryDecode(bytes);
}
private static class PublicKeyDecoder
{
private enum AlgorithmClass
{
Signature = 1,
Hash = 4,
}
private enum AlgorithmSubId
{
Sha1Hash = 4,
MacHash = 5,
RipeMdHash = 6,
RipeMd160Hash = 7,
Ssl3ShaMD5Hash = 8,
HmacHash = 9,
Tls1PrfHash = 10,
HashReplacOwfHash = 11,
Sha256Hash = 12,
Sha384Hash = 13,
Sha512Hash = 14,
}
private struct AlgorithmId
{
// From wincrypt.h
private const int AlgorithmClassOffset = 13;
private const int AlgorithmClassMask = 0x7;
private const int AlgorithmSubIdOffset = 0;
private const int AlgorithmSubIdMask = 0x1ff;
private readonly uint flags;
public bool IsSet
{
get { return flags != 0; }
}
public AlgorithmClass Class
{
get { return (AlgorithmClass)((flags >> AlgorithmClassOffset) & AlgorithmClassMask); }
}
public AlgorithmSubId SubId
{
get { return (AlgorithmSubId)((flags >> AlgorithmSubIdOffset) & AlgorithmSubIdMask); }
}
public AlgorithmId(uint flags)
{
this.flags = flags;
}
}
// From ECMAKey.h
private static readonly ImmutableArray<byte> ecmaKey = ImmutableArray.Create(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0 });
// From strongname.h
//
// The public key blob has the following format as a little-endian packed C struct:
//
// struct
// {
// uint32_t SigAlgId; // Signature algorithm ID
// uint32_t HashAlgId; // Hash algorithm ID
// uint32_t PublicKeySize; // Size of public key data in bytes, not including the header
// uint8_t PublicKey[0]; // PublicKeySize bytes of publc key data
// }
//
// The offsets of each relevant field are recorded below.
private const int SigAlgIdOffset = 0;
private const int HashAlgIdOffset = SigAlgIdOffset + sizeof(uint);
private const int PublicKeySizeOffset = HashAlgIdOffset + sizeof(uint);
private const int PublicKeyDataOffset = PublicKeySizeOffset + sizeof(uint);
private const int HeaderSize = PublicKeyDataOffset;
// From wincrypt.h
private const byte PublicKeyBlob = 0x06;
private static uint ToUInt32(ImmutableArray<byte> bytes, int offset)
{
Debug.Assert((bytes.Length - offset) > sizeof(int));
return (uint)((int)bytes[offset] | ((int)bytes[offset + 1] << 8) | ((int)bytes[offset + 2] << 16) | ((int)bytes[offset + 3] << 24));
}
// From StrongNameInternal.cpp
public static bool TryDecode(ImmutableArray<byte> bytes)
{
// The number of public key bytes must be at least large enough for the header and one byte of data.
if (bytes.IsDefault || bytes.Length < HeaderSize + 1)
{
return false;
}
// The number of public key bytes must be the same as the size of the header plus the size of the public key data.
var dataSize = ToUInt32(bytes, PublicKeySizeOffset);
if (bytes.Length != HeaderSize + dataSize)
{
return false;
}
// Check for the ECMA key, which does not obey the invariants checked below.
if (ByteSequenceComparer.Equals(bytes, ecmaKey))
{
return true;
}
var signatureAlgorithmId = new AlgorithmId(ToUInt32(bytes, 0));
if (signatureAlgorithmId.IsSet && signatureAlgorithmId.Class != AlgorithmClass.Signature)
{
return false;
}
var hashAlgorithmId = new AlgorithmId(ToUInt32(bytes, 4));
if (hashAlgorithmId.IsSet && (hashAlgorithmId.Class != AlgorithmClass.Hash || hashAlgorithmId.SubId < AlgorithmSubId.Sha1Hash))
{
return false;
}
if (bytes[PublicKeyDataOffset] != PublicKeyBlob)
{
return false;
}
return true;
}
}
}
}
......@@ -389,7 +389,8 @@ private static ImmutableArray<AssemblyIdentity> GetReferencedAssembliesOrThrow(M
reference.Flags,
reference.PublicKeyOrToken,
reference.Name,
reference.Culture));
reference.Culture,
isReference: true));
}
return result.ToImmutable();
......@@ -453,7 +454,8 @@ internal AssemblyIdentity ReadAssemblyIdentityOrThrow()
assemblyDef.Flags,
assemblyDef.PublicKey,
assemblyDef.Name,
assemblyDef.Culture);
assemblyDef.Culture,
isReference: false);
}
/// <exception cref="BadImageFormatException">An exception from metadata reader.</exception>
......@@ -463,7 +465,8 @@ internal AssemblyIdentity ReadAssemblyIdentityOrThrow()
AssemblyFlags flags,
BlobHandle publicKey,
StringHandle name,
StringHandle culture)
StringHandle culture,
bool isReference)
{
string nameStr = reader.GetString(name);
if (!MetadataHelpers.IsValidMetadataIdentifier(nameStr))
......@@ -477,12 +480,35 @@ internal AssemblyIdentity ReadAssemblyIdentityOrThrow()
throw new BadImageFormatException(string.Format(CodeAnalysisResources.InvalidCultureName, cultureName));
}
var hasPublicKey = (flags & AssemblyFlags.PublicKey) != 0;
var publicKeyOrToken = !publicKey.IsNil ? reader.GetBlobBytes(publicKey).AsImmutableOrNull() : default(ImmutableArray<byte>);
if (hasPublicKey)
{
if (!MetadataHelpers.IsValidPublicKey(publicKeyOrToken))
{
throw new BadImageFormatException(CodeAnalysisResources.InvalidPublicKey);
}
}
else if (isReference)
{
if (!publicKeyOrToken.IsDefaultOrEmpty && publicKeyOrToken.Length != AssemblyIdentity.PublicKeyTokenSize)
{
throw new BadImageFormatException(CodeAnalysisResources.InvalidPublicKeyToken);
}
}
else
{
// Assembly definitions do not contain public key tokens, but they may contain public key
// data without being marked as strong name signed (e.g. delay-signed assemblies).
publicKeyOrToken = default(ImmutableArray<byte>);
}
return new AssemblyIdentity(
name: nameStr,
version: version,
cultureName: cultureName,
publicKeyOrToken: (!publicKey.IsNil) ? reader.GetBlobBytes(publicKey).AsImmutableOrNull() : default(ImmutableArray<byte>),
hasPublicKey: (flags & AssemblyFlags.PublicKey) != 0,
publicKeyOrToken: publicKeyOrToken,
hasPublicKey: hasPublicKey,
isRetargetable: (flags & AssemblyFlags.Retargetable) != 0,
contentType: (AssemblyContentType)((int)(flags & AssemblyFlags.ContentTypeMask) >> 9),
noThrow: true);
......
......@@ -5,7 +5,6 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis.Collections;
using Roslyn.Utilities;
......@@ -611,152 +610,25 @@ internal static bool TryParseVersion(string str, out ulong result, out AssemblyI
}
}
private static class PublicKeyDecoder
{
private enum AlgorithmClass
{
Signature = 1,
Hash = 4,
}
private enum AlgorithmSubId
{
Sha1Hash = 4,
MacHash = 5,
RipeMdHash = 6,
RipeMd160Hash = 7,
Ssl3ShaMD5Hash = 8,
HmacHash = 9,
Tls1PrfHash = 10,
HashReplacOwfHash = 11,
Sha256Hash = 12,
Sha384Hash = 13,
Sha512Hash = 14,
}
private struct AlgorithmId
{
// From wincrypt.h
private const int AlgorithmClassOffset = 13;
private const int AlgorithmClassMask = 0x7;
private const int AlgorithmSubIdOffset = 0;
private const int AlgorithmSubIdMask = 0x1ff;
private readonly uint flags;
public bool IsSet
{
get { return flags != 0; }
}
public AlgorithmClass Class
{
get { return (AlgorithmClass)((flags >> AlgorithmClassOffset) & AlgorithmClassMask); }
}
public AlgorithmSubId SubId
{
get { return (AlgorithmSubId)((flags >> AlgorithmSubIdOffset) & AlgorithmSubIdMask); }
}
public AlgorithmId(uint flags)
{
this.flags = flags;
}
}
// From ECMAKey.h
private static readonly ImmutableArray<byte> ecmaKey = ImmutableArray.Create(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0 });
// From strongname.h
[StructLayout(LayoutKind.Sequential, Pack = 0)]
private unsafe struct PublicKeyHeader
{
public const int SigAlgIdOffset = 0;
public const int HashAlgIdOffset = SigAlgIdOffset + sizeof(uint);
public const int PublicKeySizeOffset = HashAlgIdOffset + sizeof(uint);
public const int PublicKeyDataOffset = PublicKeySizeOffset + sizeof(uint);
public const int Size = PublicKeyDataOffset;
uint SigAlgId;
uint HashAlgId;
uint PublicKeySize;
}
// From wincrypt.h
private const byte PublicKeyBlob = 0x06;
// From StrongNameInternal.cpp
public static bool TryDecode(byte[] bytes, out ImmutableArray<byte> key)
{
// The number of public key bytes must be at least large enough for the header and one byte of data.
if (bytes.Length < PublicKeyHeader.Size + 1)
{
key = default(ImmutableArray<byte>);
return false;
}
// The number of public key bytes must be the same as the size of the header plus the size of the public key data.
var dataSize = (uint)BitConverter.ToInt32(bytes, PublicKeyHeader.PublicKeySizeOffset);
if (bytes.Length != PublicKeyHeader.Size + dataSize)
{
key = default(ImmutableArray<byte>);
return false;
}
// Check for ECMA key
if (bytes.Length == ecmaKey.Length)
{
for (int i = 0; i < bytes.Length; i++)
{
if (bytes[i] != ecmaKey[i])
{
goto notEcmaKey;
}
}
key = ecmaKey;
return true;
}
notEcmaKey:
var signatureAlgorithmId = new AlgorithmId((uint)BitConverter.ToInt32(bytes, 0));
if (signatureAlgorithmId.IsSet && signatureAlgorithmId.Class != AlgorithmClass.Signature)
{
key = default(ImmutableArray<byte>);
return false;
}
var hashAlgorithmId = new AlgorithmId((uint)BitConverter.ToInt32(bytes, 4));
if (hashAlgorithmId.IsSet && (hashAlgorithmId.Class != AlgorithmClass.Hash || hashAlgorithmId.SubId < AlgorithmSubId.Sha1Hash))
{
key = default(ImmutableArray<byte>);
return false;
}
if (bytes[PublicKeyHeader.PublicKeyDataOffset] != PublicKeyBlob)
{
key = default(ImmutableArray<byte>);
return false;
}
key = bytes.AsImmutable();
return true;
}
}
const int MaxPublicKeyBytes = 2048;
private static bool TryParsePublicKey(string value, out ImmutableArray<byte> key)
{
byte[] result;
ImmutableArray<byte> result;
if (value.Length > (MaxPublicKeyBytes * 2) || !TryParseHexBytes(value, out result))
{
key = default(ImmutableArray<byte>);
return false;
}
return PublicKeyDecoder.TryDecode(result, out key);
if (!MetadataHelpers.IsValidPublicKey(result))
{
key = default(ImmutableArray<byte>);
return false;
}
key = result;
return true;
}
const int PublicKeyTokenBytes = 8;
......@@ -770,41 +642,43 @@ private static bool TryParsePublicKeyToken(string value, out ImmutableArray<byte
return true;
}
byte[] result;
ImmutableArray<byte> result;
if (value.Length != (PublicKeyTokenBytes * 2) || !TryParseHexBytes(value, out result))
{
token = default(ImmutableArray<byte>);
return false;
}
token = result.AsImmutable();
token = result;
return true;
}
private static bool TryParseHexBytes(string value, out byte[] result)
private static bool TryParseHexBytes(string value, out ImmutableArray<byte> result)
{
if (value.Length == 0 || (value.Length % 2) != 0)
{
result = null;
result = default(ImmutableArray<byte>);
return false;
}
var bytes = new byte[value.Length / 2];
for (int i = 0; i < bytes.Length; i++)
var length = value.Length / 2;
var bytes = ArrayBuilder<byte>.GetInstance(length);
for (int i = 0; i < length; i++)
{
int hi = HexValue(value[i * 2]);
int lo = HexValue(value[i * 2 + 1]);
if (hi < 0 || lo < 0)
{
result = null;
result = default(ImmutableArray<byte>);
bytes.Free();
return false;
}
bytes[i] = (byte)((hi << 4) | lo);
bytes.Add((byte)((hi << 4) | lo));
}
result = bytes;
result = bytes.ToImmutableAndFree();
return true;
}
......
......@@ -48,7 +48,7 @@ public sealed partial class AssemblyIdentity : IEquatable<AssemblyIdentity>
// cached hash code
private int lazyHashCode;
private const int PublicKeyTokenSize = 8;
internal const int PublicKeyTokenSize = 8;
/// <summary>
/// Constructs an <see cref="AssemblyIdentity"/> from its constituent parts.
......@@ -98,9 +98,9 @@ public sealed partial class AssemblyIdentity : IEquatable<AssemblyIdentity>
if (hasPublicKey)
{
if (publicKeyOrToken.IsDefaultOrEmpty)
if (!MetadataHelpers.IsValidPublicKey(publicKeyOrToken))
{
throw new ArgumentException(CodeAnalysisResources.ExpectedNonEmptyPublicKey, "publicKeyOrToken");
throw new ArgumentException(CodeAnalysisResources.InvalidPublicKey, "publicKeyOrToken");
}
}
else
......@@ -135,7 +135,7 @@ public sealed partial class AssemblyIdentity : IEquatable<AssemblyIdentity>
Debug.Assert(IsValidName(name));
Debug.Assert(IsValid(version));
Debug.Assert(IsValidCultureName(cultureName));
Debug.Assert((hasPublicKey && !publicKeyOrToken.IsDefaultOrEmpty) || (publicKeyOrToken.IsDefaultOrEmpty || publicKeyOrToken.Length == PublicKeyTokenSize));
Debug.Assert((hasPublicKey && MetadataHelpers.IsValidPublicKey(publicKeyOrToken)) || (!hasPublicKey && (publicKeyOrToken.IsDefaultOrEmpty || publicKeyOrToken.Length == PublicKeyTokenSize)));
this.name = name;
this.version = version ?? NullVersion;
......@@ -145,7 +145,7 @@ public sealed partial class AssemblyIdentity : IEquatable<AssemblyIdentity>
InitializeKey(publicKeyOrToken, hasPublicKey, out this.publicKey, out this.lazyPublicKeyToken);
}
// error-tolerant constructor used by metadata reader:
// constructor used by metadata reader:
internal AssemblyIdentity(
string name,
Version version,
......@@ -157,32 +157,15 @@ public sealed partial class AssemblyIdentity : IEquatable<AssemblyIdentity>
bool noThrow)
{
Debug.Assert(!string.IsNullOrEmpty(name));
Debug.Assert((hasPublicKey && MetadataHelpers.IsValidPublicKey(publicKeyOrToken)) || (!hasPublicKey && (publicKeyOrToken.IsDefaultOrEmpty || publicKeyOrToken.Length == PublicKeyTokenSize)));
Debug.Assert(noThrow);
if (hasPublicKey)
{
if (publicKeyOrToken.IsDefaultOrEmpty)
{
// PublicKey flag but no key specified => assume the flag is wrong:
hasPublicKey = false;
}
}
else
{
if (!publicKeyOrToken.IsDefaultOrEmpty && publicKeyOrToken.Length != PublicKeyTokenSize)
{
// token specified but its size isn't correct => assume it's the full key:
hasPublicKey = true;
}
}
InitializeKey(publicKeyOrToken, hasPublicKey, out this.publicKey, out this.lazyPublicKeyToken);
this.name = name;
this.version = version ?? NullVersion;
this.cultureName = cultureName ?? string.Empty;
this.contentType = IsValid(contentType) ? contentType : AssemblyContentType.Default;
this.isRetargetable = isRetargetable && this.contentType != AssemblyContentType.WindowsRuntime;
InitializeKey(publicKeyOrToken, hasPublicKey, out this.publicKey, out this.lazyPublicKeyToken);
}
static private void InitializeKey(ImmutableArray<byte> publicKeyOrToken, bool hasPublicKey,
......
......@@ -153,5 +153,16 @@ Namespace TestResources.SymbolsTests
Return CType(obj,Byte())
End Get
End Property
'''<summary>
''' Looks up a localized resource of type System.Byte[].
'''</summary>
Public Shared ReadOnly Property InvalidPublicKey() As Byte()
Get
Dim obj As Object = ResourceManager.GetObject("InvalidPublicKey", resourceCulture)
Return CType(obj,Byte())
End Get
End Property
End Class
End Namespace
......@@ -147,4 +147,7 @@
<data name="MscorlibNamespacesAndTypes" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>MscorlibNamespacesAndTypes.bsl;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>
\ No newline at end of file
<data name="InvalidPublicKey" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>InvalidPublicKey.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册