Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate IntPtr and ByRef field overlap #65246

Merged
238 changes: 161 additions & 77 deletions src/coreclr/vm/methodtablebuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8394,7 +8394,6 @@ MethodTableBuilder::HandleExplicitLayout(
// Instance slice size is the total size of an instance, and is calculated as
// the field whose offset and size add to the greatest number.
UINT instanceSliceSize = 0;
DWORD firstObjectOverlapOffset = ((DWORD)(-1));

UINT i;
for (i = 0; i < bmtMetaData->cFields; i++)
Expand Down Expand Up @@ -8433,15 +8432,19 @@ MethodTableBuilder::HandleExplicitLayout(
//
// 1. Verify that every OREF or BYREF is on a valid alignment.
// 2. Verify that OREFs only overlap with other OREFs.
// 3. If an OREF does overlap with another OREF, the class is marked unverifiable.
// 4. If an overlap of any kind occurs, the class will be marked NotTightlyPacked (affects ValueType.Equals()).
// 3. Verify that BYREFs only overlap with other BYREFs.
// 4. If an OREF does overlap with another OREF, the class is marked unverifiable.
// 5. If a BYREF does overlap with another BYREF, the class is marked unverifiable.
// 6. If an overlap of any kind occurs, the class will be marked NotTightlyPacked (affects ValueType.Equals()).
//
char emptyObject[TARGET_POINTER_SIZE];
char isObject[TARGET_POINTER_SIZE];
char isByRef[TARGET_POINTER_SIZE];
for (i = 0; i < TARGET_POINTER_SIZE; i++)
{
emptyObject[i] = empty;
isObject[i] = oref;
isByRef[i] = byref;
}

ExplicitClassTrust explicitClassTrust;
Expand Down Expand Up @@ -8482,7 +8485,7 @@ MethodTableBuilder::HandleExplicitLayout(
CorElementType type = pFD->GetFieldType();
if (CorTypeInfo::IsObjRef(type) || CorTypeInfo::IsByRef(type))
{
// Check that the ref offset is pointer aligned
// Check that the field is pointer aligned
if ((pFD->GetOffset_NoLogging() & ((ULONG)TARGET_POINTER_SIZE - 1)) != 0)
{
badOffset = pFD->GetOffset_NoLogging();
Expand All @@ -8491,98 +8494,101 @@ MethodTableBuilder::HandleExplicitLayout(
// If we got here, OREF or BYREF field was not pointer aligned. THROW.
break;
}
}

if (CorTypeInfo::IsObjRef(type))
{
// check if overlaps another object
if (memcmp((void *)&pFieldLayout[pFD->GetOffset_NoLogging()], (void *)isObject, sizeof(isObject)) == 0)
// Determine which tag type we are working with.
bmtFieldLayoutTag tag;
SIZE_T tagBlockSize;
void* tagBlock;
if (CorTypeInfo::IsObjRef(type))
{
// If we got here, an OREF overlapped another OREF. We permit this but mark the class unverifiable.
fieldTrust.SetTrust(ExplicitFieldTrust::kLegal);

if (firstObjectOverlapOffset == ((DWORD)(-1)))
{
firstObjectOverlapOffset = pFD->GetOffset_NoLogging();
}
tagBlockSize = sizeof(isObject);
tagBlock = (void*)isObject;
tag = oref;
}
else
{
_ASSERTE(CorTypeInfo::IsByRef(type));
tagBlockSize = sizeof(isByRef);
tagBlock = (void*)isByRef;
tag = byref;
}

// Check if there is overlap with its own tag type
if (memcmp((void *)&pFieldLayout[pFD->GetOffset_NoLogging()], tagBlock, tagBlockSize) == 0)
{
// If we got here, there is tag type overlap. We permit this but mark the class unverifiable.
fieldTrust.SetTrust(ExplicitFieldTrust::kLegal);
continue;
}
// check if is empty at this point
// check if typed layout is empty at this point
if (memcmp((void *)&pFieldLayout[pFD->GetOffset_NoLogging()], (void *)emptyObject, sizeof(emptyObject)) == 0)
{
// If we got here, this OREF is overlapping no other fields (yet). Record that these bytes now contain an OREF.
memset((void *)&pFieldLayout[pFD->GetOffset_NoLogging()], oref, sizeof(isObject));
// If we got here, this tag type is overlapping no other fields (yet).
// Record that these bytes now contain the current tag type.
memset((void *)&pFieldLayout[pFD->GetOffset_NoLogging()], tag, tagBlockSize);
fieldTrust.SetTrust(ExplicitFieldTrust::kNonOverLayed);
continue;
}

// If we got here, the OREF overlaps a non-OREF. THROW.
// If we got here, the tag overlaps something else. THROW.
badOffset = pFD->GetOffset_NoLogging();
fieldTrust.SetTrust(ExplicitFieldTrust::kNone);
break;
}
else
{
UINT fieldSize;
if (pFD->IsByValue())
if (!pFD->IsByValue())
{
fieldSize = GetFieldSize(pFD);
}
else
{
MethodTable *pByValueMT = pByValueClassCache[valueClassCacheIndex];
if (pByValueMT->IsByRefLike())
if (pByValueMT->IsByRefLike() || pByValueMT->ContainsPointers())
{
if ((pFD->GetOffset_NoLogging() & ((ULONG)TARGET_POINTER_SIZE - 1)) != 0)
{
// If we got here, then a byref-like valuetype was misaligned.
// If we got here, then a ByRefLike valuetype or a valuetype containing an OREF was misaligned.
badOffset = pFD->GetOffset_NoLogging();
fieldTrust.SetTrust(ExplicitFieldTrust::kNone);
break;
}
}
if (pByValueMT->ContainsPointers())
{
if ((pFD->GetOffset_NoLogging() & ((ULONG)TARGET_POINTER_SIZE - 1)) == 0)
{
ExplicitFieldTrust::TrustLevel trust;
DWORD firstObjectOverlapOffsetInsideValueClass = ((DWORD)(-1));
trust = CheckValueClassLayout(pByValueMT, &pFieldLayout[pFD->GetOffset_NoLogging()], &firstObjectOverlapOffsetInsideValueClass);
fieldTrust.SetTrust(trust);
if (firstObjectOverlapOffsetInsideValueClass != ((DWORD)(-1)))
{
if (firstObjectOverlapOffset == ((DWORD)(-1)))
{
firstObjectOverlapOffset = pFD->GetOffset_NoLogging() + firstObjectOverlapOffsetInsideValueClass;
}
}

if (trust != ExplicitFieldTrust::kNone)
{
continue;
}
else
{
// If we got here, then an OREF inside the valuetype illegally overlapped a non-OREF field. THROW.
badOffset = pFD->GetOffset_NoLogging();
break;
}
ExplicitFieldTrust::TrustLevel trust = CheckValueClassLayout(pByValueMT, &pFieldLayout[pFD->GetOffset_NoLogging()]);
fieldTrust.SetTrust(trust);

if (trust != ExplicitFieldTrust::kNone)
{
continue;
}
else
{
// If we got here, then an OREF/BYREF inside the valuetype illegally overlapped a non-OREF field. THROW.
badOffset = pFD->GetOffset_NoLogging();
break;
}
// If we got here, then a valuetype containing an OREF was misaligned.
badOffset = pFD->GetOffset_NoLogging();
fieldTrust.SetTrust(ExplicitFieldTrust::kNone);
break;
}
// no pointers so fall through to do standard checking
fieldSize = pByValueMT->GetNumInstanceFieldBytes();
}
else

// If we got here, we are trying to place a non-OREF (or a valuetype composed of non-OREFs.)
// Look for any orefs or byrefs under this field
BYTE *loc = NULL;
BYTE* currOffset = pFieldLayout + pFD->GetOffset_NoLogging();
BYTE* endOffset = currOffset + fieldSize;
for (; currOffset < endOffset; ++currOffset)
{
// field size temporarily stored in pInterface field
fieldSize = GetFieldSize(pFD);
if (*currOffset == oref || *currOffset == byref)
{
loc = currOffset;
break;
}
}

// If we got here, we are trying to place a non-OREF (or a valuetype composed of non-OREFs.)
// Look for any orefs under this field
BYTE *loc;
if ((loc = (BYTE*)memchr((void*)&pFieldLayout[pFD->GetOffset_NoLogging()], oref, fieldSize)) == NULL)
if (loc == NULL)
{
// If we have a nonoref in the range then we are doing an overlay
if(memchr((void*)&pFieldLayout[pFD->GetOffset_NoLogging()], nonoref, fieldSize))
Expand All @@ -8598,7 +8604,7 @@ MethodTableBuilder::HandleExplicitLayout(
}

// If we got here, we tried to place a non-OREF (or a valuetype composed of non-OREFs)
// on top of an OREF. THROW.
// on top of an OREF/BYREF. THROW.
badOffset = (UINT)(loc - pFieldLayout);
fieldTrust.SetTrust(ExplicitFieldTrust::kNone);
break;
Expand Down Expand Up @@ -8716,27 +8722,29 @@ MethodTableBuilder::HandleExplicitLayout(
} // MethodTableBuilder::HandleExplicitLayout

//*******************************************************************************
// make sure that no object fields are overlapped incorrectly, returns S_FALSE if
// there overlap but nothing illegal, S_OK if there is no overlap
/*static*/ ExplicitFieldTrust::TrustLevel MethodTableBuilder::CheckValueClassLayout(MethodTable * pMT, BYTE *pFieldLayout, DWORD *pFirstObjectOverlapOffset)
// make sure that no object fields are overlapped incorrectly, returns the trust level
/*static*/ ExplicitFieldTrust::TrustLevel MethodTableBuilder::CheckValueClassLayout(MethodTable * pMT, BYTE *pFieldLayout)
{
STANDARD_VM_CONTRACT;

// ByRefLike types need to be checked for ByRef fields.
if (pMT->IsByRefLike())
return CheckByRefLikeValueClassLayout(pMT, pFieldLayout);

*pFirstObjectOverlapOffset = (DWORD)(-1);
// This method assumes there is a GC desc associated with the MethodTable.
_ASSERTE(pMT->ContainsPointers());

// Build a layout of the value class. Don't know the sizes of all the fields easily, but
// do know a) vc is already consistent so don't need to check it's overlaps and
// b) size and location of all objectrefs. So build it by setting all non-oref
// Build a layout of the value class (vc). Don't know the sizes of all the fields easily, but
// do know (a) vc is already consistent so don't need to check it's overlaps and
// (b) size and location of all objectrefs. So build it by setting all non-oref
// then fill in the orefs later
UINT fieldSize = pMT->GetNumInstanceFieldBytes();

CQuickBytes qb;
BYTE *vcLayout = (BYTE*) qb.AllocThrows(fieldSize * sizeof(BYTE));

memset((void*)vcLayout, nonoref, fieldSize);

// use pointer series to locate the orefs

CGCDesc* map = CGCDesc::GetCGCDescFromMT(pMT);
CGCDescSeries *pSeries = map->GetLowestSeries();

Expand All @@ -8748,11 +8756,9 @@ MethodTableBuilder::HandleExplicitLayout(
pSeries++;
}


ExplicitClassTrust explicitClassTrust;

for (UINT i=0; i < fieldSize; i++) {

for (UINT i=0; i < fieldSize; i++)
{
ExplicitFieldTrustHolder fieldTrust(&explicitClassTrust);

if (vcLayout[i] == oref) {
Expand All @@ -8771,10 +8777,6 @@ MethodTableBuilder::HandleExplicitLayout(
// oref <--> oref
case oref:
fieldTrust.SetTrust(ExplicitFieldTrust::kLegal);
if ((*pFirstObjectOverlapOffset) == ((DWORD)(-1)))
{
*pFirstObjectOverlapOffset = (DWORD)i;
}
break;

default:
Expand Down Expand Up @@ -8810,10 +8812,92 @@ MethodTableBuilder::HandleExplicitLayout(
}


//*******************************************************************************
// make sure that no byref/object fields are overlapped, returns the trust level
/*static*/ ExplicitFieldTrust::TrustLevel MethodTableBuilder::CheckByRefLikeValueClassLayout(MethodTable * pMT, BYTE *pFieldLayout)
{
STANDARD_VM_CONTRACT;
_ASSERTE(pMT->IsByRefLike());

// ByReference<T> is an indication for a byref field, treat it as such.
if (pMT->HasSameTypeDefAs(g_pByReferenceClass))
return MarkTagType(pFieldLayout, TARGET_POINTER_SIZE, byref);

ExplicitClassTrust explicitClassTrust;

ExplicitFieldTrust::TrustLevel trust;
ApproxFieldDescIterator fieldIterator(pMT, ApproxFieldDescIterator::INSTANCE_FIELDS);
for (FieldDesc *pFD = fieldIterator.Next(); pFD != NULL; pFD = fieldIterator.Next())
{
ExplicitFieldTrustHolder fieldTrust(&explicitClassTrust);
int fieldStartIndex = pFD->GetOffset();

if (pFD->GetFieldType() == ELEMENT_TYPE_VALUETYPE)
{
MethodTable *pFieldMT = pFD->GetApproxFieldTypeHandleThrowing().AsMethodTable();
trust = CheckValueClassLayout(pFieldMT, &pFieldLayout[fieldStartIndex]);
}
else if (pFD->IsObjRef())
{
_ASSERTE(fieldStartIndex % TARGET_POINTER_SIZE == 0);
trust = MarkTagType(&pFieldLayout[fieldStartIndex], TARGET_POINTER_SIZE, oref);
}
else if (pFD->IsByRef())
{
_ASSERTE(fieldStartIndex % TARGET_POINTER_SIZE == 0);
trust = MarkTagType(&pFieldLayout[fieldStartIndex], TARGET_POINTER_SIZE, byref);
}
else
{
trust = MarkTagType(&pFieldLayout[fieldStartIndex], pFD->GetSize(), nonoref);
}

fieldTrust.SetTrust(trust);

// Some invalid overlap was detected.
if (trust == ExplicitFieldTrust::kNone)
break;
}

return explicitClassTrust.GetTrustLevel();
}

//*******************************************************************************
// Set the field's tag type and/or detect invalid overlap
/*static*/ ExplicitFieldTrust::TrustLevel MethodTableBuilder::MarkTagType(BYTE* field, SIZE_T fieldSize, bmtFieldLayoutTag tagType)
{
STANDARD_VM_CONTRACT;
_ASSERTE(field != NULL);
_ASSERTE(fieldSize != 0);
_ASSERTE(tagType != empty);

ExplicitFieldTrust::TrustLevel trust = ExplicitFieldTrust::kMaxTrust;
for (SIZE_T i = 0; i < fieldSize; ++i)
{
if (field[i] == empty)
{
// Nothing set for overlap, mark as requested.
field[i] = tagType;
}
else if (field[i] == tagType)
{
// Only a overlapped nonoref tag is verifiable, all others are simply legal.
ExplicitFieldTrust::TrustLevel overlapTrust = tagType == nonoref ? ExplicitFieldTrust::kVerifiable : ExplicitFieldTrust::kLegal;

// The ExplicitFieldTrust enum is ranked in descending order of trust.
// We always take the computed minimum trust level.
trust = min(trust, overlapTrust);
}
else
{
// A non-equal overlap was detected. There is no trust for the type.
trust = ExplicitFieldTrust::kNone;
break;
}
}

return trust;
}

//*******************************************************************************
void MethodTableBuilder::FindPointerSeriesExplicit(UINT instanceSliceSize,
Expand Down
14 changes: 11 additions & 3 deletions src/coreclr/vm/methodtablebuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -2089,7 +2089,7 @@ class MethodTableBuilder

// --------------------------------------------------------------------------------------------
// Used for analyzing overlapped fields defined by explicit layout types.
enum bmtFieldLayoutTag {empty, nonoref, oref};
enum bmtFieldLayoutTag {empty, nonoref, oref, byref};

// --------------------------------------------------------------------------------------------
// used for calculating pointer series for tdexplicit
Expand Down Expand Up @@ -2932,8 +2932,16 @@ class MethodTableBuilder

static ExplicitFieldTrust::TrustLevel CheckValueClassLayout(
MethodTable * pMT,
BYTE * pFieldLayout,
DWORD * pFirstObjectOverlapOffset);
BYTE * pFieldLayout);

static ExplicitFieldTrust::TrustLevel CheckByRefLikeValueClassLayout(
MethodTable * pMT,
BYTE * pFieldLayout);

static ExplicitFieldTrust::TrustLevel MarkTagType(
BYTE* field,
SIZE_T size,
bmtFieldLayoutTag tagType);

void FindPointerSeriesExplicit(
UINT instanceSliceSize,
Expand Down
Loading