Skip to content

Commit 66a6ea2

Browse files
Added memory address validation at key points
1 parent 37a301c commit 66a6ea2

File tree

2 files changed

+122
-5
lines changed

2 files changed

+122
-5
lines changed

Rubberduck.VBEEditor/ComManagement/TypeLibs/TypeLibs.cs

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,88 @@ public void AppendLineNoNullChars(string value)
4747
=> AppendLine(value.Replace("\0", string.Empty));
4848
}
4949

50+
public static class UnmanagedMemHelper
51+
{
52+
/// <summary>
53+
/// Windows API call used for memory range validation
54+
/// </summary>
55+
[DllImport("kernel32.dll")]
56+
public static extern int VirtualQuery(IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
57+
58+
/// <summary>
59+
/// Do our best to validate that the input memory address is actually a COM object
60+
/// </summary>
61+
/// <param name="comObjectPtr">the input memory address to check</param>
62+
/// <returns>false means definitely not a valid COM object. true means _probably_ a valid COM object</returns>
63+
public static bool ValidateComObject(IntPtr comObjectPtr)
64+
{
65+
// Is it a valid memory address, with at least one accessible vTable ptr
66+
if (IsValidMemoryRange(comObjectPtr, IntPtr.Size))
67+
{
68+
IntPtr vTablePtr = Marshal.ReadIntPtr(comObjectPtr);
69+
70+
// And for a COM object, we need a valid vtable, with at least 3 vTable entries (for IUnknown)
71+
if (IsValidMemoryRange(vTablePtr, IntPtr.Size * 3))
72+
{
73+
IntPtr firstvTableEntry = Marshal.ReadIntPtr(vTablePtr);
74+
75+
// And lets check the first vTable entry actually points to EXECUTABLE memory
76+
// (we could check all 3 initial IUnknown entries, but we want to be reasonably
77+
// efficient and we can never 100% guarantee our result anyway.)
78+
if (IsValidMemoryRange(firstvTableEntry, 1, checkIsExecutable: true))
79+
{
80+
// As best as we can tell, it looks to be a valid COM object
81+
return true;
82+
}
83+
}
84+
}
85+
86+
// One of the validation checks failed. The COM object is definitely not a valid COM object.
87+
return false;
88+
}
89+
90+
/// <summary>
91+
/// Validate a memory address range
92+
/// </summary>
93+
/// <param name="memOffset">the input memory address to check</param>
94+
/// <param name="size">the minimum size of data we are expecting to be available next to memOffset</param>
95+
/// <param name="checkIsExecutable">optionally check if the memory address points to EXECUTABLE memory</param>
96+
public static bool IsValidMemoryRange(IntPtr memOffset, int size, bool checkIsExecutable = false)
97+
{
98+
if (memOffset != IntPtr.Zero)
99+
{
100+
var memInfo = new MEMORY_BASIC_INFORMATION();
101+
var sizeOfMemInfo = Marshal.SizeOf(memInfo);
102+
103+
if (VirtualQuery(memOffset, out memInfo, sizeOfMemInfo) == sizeOfMemInfo)
104+
{
105+
if ((!memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_NOACCESS)) &&
106+
(!memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_GUARD)))
107+
{
108+
// We've confirmed the base memory address is valid, and is accessible.
109+
// Finally just check the full address RANGE is also valid (i.e. the end point of the structure we're reading)
110+
var validMemAddressEnd = memInfo.BaseAddress.ToInt64() + memInfo.RegionSize.ToInt64();
111+
var endOfStructPtr = memOffset.ToInt64() + size;
112+
if (endOfStructPtr <= validMemAddressEnd)
113+
{
114+
if (checkIsExecutable)
115+
{
116+
// We've been asked to check if the memory address is marked as containing executable code
117+
return memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE) ||
118+
memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE_READ) ||
119+
memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE_READWRITE) ||
120+
memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE_WRITECOPY);
121+
}
122+
return true;
123+
}
124+
}
125+
}
126+
}
127+
128+
return false;
129+
}
130+
}
131+
50132
/// <summary>
51133
/// Encapsulates reading unmanaged memory into managed structures
52134
/// </summary>
@@ -64,7 +146,7 @@ public static T ReadComObjectStructure<T>(object comObj)
64146
if (Marshal.IsComObject(comObj))
65147
{
66148
var referencesPtr = Marshal.GetIUnknownForObject(comObj);
67-
var retVal = StructHelper.ReadStructure<T>(referencesPtr);
149+
var retVal = StructHelper.ReadStructureSafe<T>(referencesPtr);
68150
Marshal.Release(referencesPtr);
69151
return retVal;
70152
}
@@ -86,6 +168,7 @@ public static T ReadStructure<T>(IntPtr memAddress)
86168
return (T)Marshal.PtrToStructure(memAddress, typeof(T));
87169
}
88170

171+
89172
/// <summary>
90173
/// Takes an unmanaged memory address and reads the unmanaged memory given by its pointer,
91174
/// with memory address validation for added protection
@@ -95,8 +178,12 @@ public static T ReadStructure<T>(IntPtr memAddress)
95178
/// <returns>the requested structure T</returns>
96179
public static T ReadStructureSafe<T>(IntPtr memAddress)
97180
{
98-
if (memAddress == IntPtr.Zero) return default(T);
99-
return (T)Marshal.PtrToStructure(memAddress, typeof(T));
181+
if (UnmanagedMemHelper.IsValidMemoryRange(memAddress, Marshal.SizeOf(typeof(T))))
182+
{
183+
return (T)Marshal.PtrToStructure(memAddress, typeof(T));
184+
}
185+
186+
return default(T);
100187
}
101188
}
102189

@@ -1330,7 +1417,6 @@ public static TypeLibWrapper FromVBProject(IVBProject vbProject)
13301417
{
13311418
// Now we've got the references object, we can read the internal object structure to grab the ITypeLib
13321419
var internalReferencesObj = StructHelper.ReadComObjectStructure<VBEReferencesObj>(references.Target);
1333-
13341420
return new TypeLibWrapper(internalReferencesObj.TypeLib);
13351421
}
13361422
}
@@ -1348,6 +1434,10 @@ private void InitCommon()
13481434
/// <param name="rawObjectPtr">The raw unamanaged ITypeLib pointer</param>
13491435
public TypeLibWrapper(IntPtr rawObjectPtr)
13501436
{
1437+
if (!UnmanagedMemHelper.ValidateComObject(rawObjectPtr))
1438+
{
1439+
throw new ArgumentException("Expected COM object, but validation failed.");
1440+
};
13511441
target_ITypeLib = (ComTypes.ITypeLib)Marshal.GetObjectForIUnknown(rawObjectPtr);
13521442
Marshal.Release(rawObjectPtr); // target_ITypeLib holds a reference to this now
13531443
InitCommon();
@@ -1663,7 +1753,7 @@ public VBETypeLibsAccessor(IVBE ide)
16631753
{
16641754
// Now we've got the references object, we can read the internal object structure to grab the ITypeLib
16651755
var internalReferencesObj = StructHelper.ReadComObjectStructure<VBEReferencesObj>(references.Target);
1666-
1756+
16671757
// Now we've got this one internalReferencesObj.typeLib, we can iterate through ALL loaded project TypeLibs
16681758
using (var typeLibIterator = new VBETypeLibsIterator(internalReferencesObj.TypeLib))
16691759
{

Rubberduck.VBEEditor/ComManagement/TypeLibs/TypeLibsAbstract.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@
44

55
namespace Rubberduck.VBEditor.ComManagement.TypeLibsAbstract
66
{
7+
[StructLayout(LayoutKind.Sequential)]
8+
public struct MEMORY_BASIC_INFORMATION
9+
{
10+
public IntPtr BaseAddress;
11+
public IntPtr AllocationBase;
12+
public uint AllocationProtect;
13+
public IntPtr RegionSize;
14+
public uint State;
15+
public ALLOCATION_PROTECTION Protect;
16+
public uint Type;
17+
}
18+
19+
public enum ALLOCATION_PROTECTION : uint
20+
{
21+
PAGE_EXECUTE = 0x00000010,
22+
PAGE_EXECUTE_READ = 0x00000020,
23+
PAGE_EXECUTE_READWRITE = 0x00000040,
24+
PAGE_EXECUTE_WRITECOPY = 0x00000080,
25+
PAGE_NOACCESS = 0x00000001,
26+
PAGE_READONLY = 0x00000002,
27+
PAGE_READWRITE = 0x00000004,
28+
PAGE_WRITECOPY = 0x00000008,
29+
PAGE_GUARD = 0x00000100,
30+
PAGE_NOCACHE = 0x00000200,
31+
PAGE_WRITECOMBINE = 0x00000400
32+
}
33+
734
[ComImport(), Guid("00020400-0000-0000-C000-000000000046")]
835
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
936
public interface IDispatch

0 commit comments

Comments
 (0)