summaryrefslogtreecommitdiff
path: root/src/jit/gschecks.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/gschecks.cpp')
-rw-r--r--src/jit/gschecks.cpp583
1 files changed, 583 insertions, 0 deletions
diff --git a/src/jit/gschecks.cpp b/src/jit/gschecks.cpp
new file mode 100644
index 0000000000..43cbb892e9
--- /dev/null
+++ b/src/jit/gschecks.cpp
@@ -0,0 +1,583 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX GSChecks XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+
+/*****************************************************************************
+ * gsGSChecksInitCookie
+ * Grabs the cookie for detecting overflow of unsafe buffers.
+ */
+void Compiler::gsGSChecksInitCookie()
+{
+ var_types type = TYP_I_IMPL;
+
+ lvaGSSecurityCookie = lvaGrabTemp(false DEBUGARG("GSSecurityCookie"));
+
+ // Prevent cookie init/check from being optimized
+ lvaSetVarAddrExposed(lvaGSSecurityCookie);
+ lvaTable[lvaGSSecurityCookie].lvType = type;
+
+ info.compCompHnd->getGSCookie(&gsGlobalSecurityCookieVal, &gsGlobalSecurityCookieAddr);
+}
+
+const unsigned NO_SHADOW_COPY = UINT_MAX;
+
+/*****************************************************************************
+ * gsCopyShadowParams
+ * The current function has an unsafe buffer on the stack. Search for vulnerable
+ * parameters which could be used to modify a code address and take over the process
+ * in the case of a buffer overrun. Create a safe local copy for each vulnerable parameter,
+ * which will be allocated bellow the unsafe buffer. Change uses of the param to the
+ * shadow copy.
+ *
+ * A pointer under indirection is considered vulnerable. A malicious user could read from
+ * protected memory or write to it. If a parameter is assigned/computed into another variable,
+ * and is a pointer (i.e., under indirection), then we consider the variable to be part of the
+ * equivalence class with the parameter. All parameters in the equivalence class are shadowed.
+ */
+void Compiler::gsCopyShadowParams()
+{
+ if (info.compIsVarArgs)
+ {
+ return;
+ }
+
+ // Allocate array for shadow param info
+ gsShadowVarInfo = new (this, CMK_Unknown) ShadowParamVarInfo[lvaCount]();
+
+ // Find groups of variables assigned to each other, and also
+ // tracks variables which are dereferenced and marks them as ptrs.
+ // Look for assignments to *p, and ptrs passed to functions
+ if (gsFindVulnerableParams())
+ {
+ // Replace vulnerable params by shadow copies.
+ gsParamsToShadows();
+ }
+}
+
+// This struct tracks how a tree is being used
+
+struct MarkPtrsInfo
+{
+ Compiler* comp;
+ unsigned lvAssignDef; // Which local variable is the tree being assigned to?
+ bool isAssignSrc; // Is this the source value for an assignment?
+ bool isUnderIndir; // Is this a pointer value tree that is being dereferenced?
+ bool skipNextNode; // Skip a single node during the tree-walk
+
+#ifdef DEBUG
+ void Print()
+ {
+ printf(
+ "[MarkPtrsInfo] = {comp = %p, lvAssignDef = %d, isAssignSrc = %d, isUnderIndir = %d, skipNextNode = %d}\n",
+ comp, lvAssignDef, isAssignSrc, isUnderIndir, skipNextNode);
+ }
+#endif
+};
+
+/*****************************************************************************
+ * gsMarkPtrsAndAssignGroups
+ * Walk a tree looking for assignment groups, variables whose value is used
+ * in a *p store or use, and variable passed to calls. This info is then used
+ * to determine parameters which are vulnerable.
+ * This function carries a state to know if it is under an assign node, call node
+ * or indirection node. It starts a new tree walk for it's subtrees when the state
+ * changes.
+ */
+Compiler::fgWalkResult Compiler::gsMarkPtrsAndAssignGroups(GenTreePtr* pTree, fgWalkData* data)
+{
+ struct MarkPtrsInfo* pState = (MarkPtrsInfo*)data->pCallbackData;
+ struct MarkPtrsInfo newState = *pState;
+ Compiler* comp = data->compiler;
+ GenTreePtr tree = *pTree;
+ ShadowParamVarInfo* shadowVarInfo = pState->comp->gsShadowVarInfo;
+ assert(shadowVarInfo);
+ bool fIsBlk = false;
+ unsigned lclNum;
+
+ assert(!pState->isAssignSrc || pState->lvAssignDef != (unsigned)-1);
+
+ if (pState->skipNextNode)
+ {
+ pState->skipNextNode = false;
+ return WALK_CONTINUE;
+ }
+
+ switch (tree->OperGet())
+ {
+ // Indirections - look for *p uses and defs
+ case GT_IND:
+ case GT_OBJ:
+ case GT_ARR_ELEM:
+ case GT_ARR_INDEX:
+ case GT_ARR_OFFSET:
+ case GT_FIELD:
+
+ newState.isUnderIndir = true;
+ {
+ newState.skipNextNode = true; // Don't have to worry about which kind of node we're dealing with
+ comp->fgWalkTreePre(&tree, comp->gsMarkPtrsAndAssignGroups, (void *)&newState);
+ }
+
+ return WALK_SKIP_SUBTREES;
+
+ // local vars and param uses
+ case GT_LCL_VAR:
+ case GT_LCL_FLD:
+ lclNum = tree->gtLclVarCommon.gtLclNum;
+
+ if (pState->isUnderIndir)
+ {
+ // The variable is being dereferenced for a read or a write.
+ comp->lvaTable[lclNum].lvIsPtr = 1;
+ }
+
+ if (pState->isAssignSrc)
+ {
+ //
+ // Add lvAssignDef and lclNum to a common assign group
+ if (shadowVarInfo[pState->lvAssignDef].assignGroup)
+ {
+ if (shadowVarInfo[lclNum].assignGroup)
+ {
+ // OR both bit vector
+ shadowVarInfo[pState->lvAssignDef].assignGroup->bitVectOr(shadowVarInfo[lclNum].assignGroup);
+ }
+ else
+ {
+ shadowVarInfo[pState->lvAssignDef].assignGroup->bitVectSet(lclNum);
+ }
+
+ // Point both to the same bit vector
+ shadowVarInfo[lclNum].assignGroup = shadowVarInfo[pState->lvAssignDef].assignGroup;
+ }
+ else if (shadowVarInfo[lclNum].assignGroup)
+ {
+ shadowVarInfo[lclNum].assignGroup->bitVectSet(pState->lvAssignDef);
+
+ // Point both to the same bit vector
+ shadowVarInfo[pState->lvAssignDef].assignGroup = shadowVarInfo[lclNum].assignGroup;
+ }
+ else
+ {
+ FixedBitVect* bv = FixedBitVect::bitVectInit(pState->comp->lvaCount, pState->comp);
+
+ // (shadowVarInfo[pState->lvAssignDef] == NULL && shadowVarInfo[lclNew] == NULL);
+ // Neither of them has an assign group yet. Make a new one.
+ shadowVarInfo[pState->lvAssignDef].assignGroup = bv;
+ shadowVarInfo[lclNum].assignGroup = bv;
+ bv->bitVectSet(pState->lvAssignDef);
+ bv->bitVectSet(lclNum);
+ }
+ }
+ return WALK_CONTINUE;
+
+ // Calls - Mark arg variables
+ case GT_CALL:
+
+ newState.isUnderIndir = false;
+ newState.isAssignSrc = false;
+ {
+ if (tree->gtCall.gtCallObjp)
+ {
+ newState.isUnderIndir = true;
+ comp->fgWalkTreePre(&tree->gtCall.gtCallObjp, gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+
+ for (GenTreeArgList* args = tree->gtCall.gtCallArgs; args; args = args->Rest())
+ {
+ comp->fgWalkTreePre(&args->Current(), gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+ for (GenTreeArgList* args = tree->gtCall.gtCallLateArgs; args; args = args->Rest())
+ {
+ comp->fgWalkTreePre(&args->Current(), gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+
+ if (tree->gtCall.gtCallType == CT_INDIRECT)
+ {
+ newState.isUnderIndir = true;
+
+ // A function pointer is treated like a write-through pointer since
+ // it controls what code gets executed, and so indirectly can cause
+ // a write to memory.
+ comp->fgWalkTreePre(&tree->gtCall.gtCallAddr, gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+ }
+ return WALK_SKIP_SUBTREES;
+
+ case GT_ADDR:
+ newState.isUnderIndir = false;
+ // We'll assume p in "**p = " can be vulnerable because by changing 'p', someone
+ // could control where **p stores to.
+ {
+ comp->fgWalkTreePre(&tree->gtOp.gtOp1, comp->gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+ return WALK_SKIP_SUBTREES;
+
+ default:
+ // Assignments - track assign groups and *p defs.
+ if (tree->OperIsAssignment())
+ {
+ bool isLocVar;
+ bool isLocFld;
+
+ if (tree->OperIsBlkOp())
+ {
+ // Blk assignments are always handled as if they have implicit indirections.
+ // TODO-1stClassStructs: improve this.
+ newState.isUnderIndir = true;
+ comp->fgWalkTreePre(&tree->gtOp.gtOp1, comp->gsMarkPtrsAndAssignGroups, (void*)&newState);
+
+ if (tree->OperIsInitBlkOp())
+ {
+ newState.isUnderIndir = false;
+ }
+ comp->fgWalkTreePre(&tree->gtOp.gtOp2, comp->gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+ else
+ {
+ // Walk dst side
+ comp->fgWalkTreePre(&tree->gtOp.gtOp1, comp->gsMarkPtrsAndAssignGroups, (void*)&newState);
+
+ // Now handle src side
+ isLocVar = tree->gtOp.gtOp1->OperGet() == GT_LCL_VAR;
+ isLocFld = tree->gtOp.gtOp1->OperGet() == GT_LCL_FLD;
+
+ if ((isLocVar || isLocFld) && tree->gtOp.gtOp2)
+ {
+ lclNum = tree->gtOp.gtOp1->gtLclVarCommon.gtLclNum;
+ newState.lvAssignDef = lclNum;
+ newState.isAssignSrc = true;
+ }
+
+ comp->fgWalkTreePre(&tree->gtOp.gtOp2, comp->gsMarkPtrsAndAssignGroups, (void*)&newState);
+ }
+
+ return WALK_SKIP_SUBTREES;
+ }
+ }
+
+ return WALK_CONTINUE;
+}
+
+/*****************************************************************************
+ * gsFindVulnerableParams
+ * Walk all the trees looking for ptrs, args, assign groups, *p stores, etc.
+ * Then use that info to figure out vulnerable pointers.
+ *
+ * It returns true if it found atleast one vulnerable pointer parameter that
+ * needs to be shadow-copied.
+ */
+
+bool Compiler::gsFindVulnerableParams()
+{
+ MarkPtrsInfo info;
+
+ info.comp = this;
+ info.lvAssignDef = (unsigned)-1;
+ info.isUnderIndir = false;
+ info.isAssignSrc = false;
+ info.skipNextNode = false;
+
+ // Walk all the trees setting lvIsWritePtr, lvIsOutgoingArg, lvIsPtr and assignGroup.
+ fgWalkAllTreesPre(gsMarkPtrsAndAssignGroups, &info);
+
+ // Compute has vulnerable at the end of the loop.
+ bool hasOneVulnerable = false;
+
+ // Initialize propagated[v0...vn] = {0}^n, so we can skip the ones propagated through
+ // some assign group.
+ FixedBitVect* propagated = (lvaCount > 0) ? FixedBitVect::bitVectInit(lvaCount, this) : nullptr;
+
+ for (UINT lclNum = 0; lclNum < lvaCount; lclNum++)
+ {
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+ ShadowParamVarInfo* shadowInfo = &gsShadowVarInfo[lclNum];
+
+ // If there was an indirection or if unsafe buffer, then we'd call it vulnerable.
+ if (varDsc->lvIsPtr || varDsc->lvIsUnsafeBuffer)
+ {
+ hasOneVulnerable = true;
+ }
+
+ // Now, propagate the info through the assign group (an equivalence class of vars transitively assigned.)
+ if (shadowInfo->assignGroup == nullptr || propagated->bitVectTest(lclNum))
+ {
+ continue;
+ }
+
+ // Propagate lvIsPtr, so that:
+ // 1. Any parameter in the equivalence class can be identified as lvIsPtr and hence shadowed.
+ // 2. Buffers with pointers are placed at lower memory addresses than buffers without pointers.
+ UINT isUnderIndir = varDsc->lvIsPtr;
+
+ // First pass -- find if any variable is vulnerable.
+ FixedBitVect* assignGroup = shadowInfo->assignGroup;
+ for (UINT lclNum = assignGroup->bitVectGetFirst(); lclNum != (unsigned)-1 && !isUnderIndir;
+ lclNum = assignGroup->bitVectGetNext(lclNum))
+ {
+ isUnderIndir |= lvaTable[lclNum].lvIsPtr;
+ }
+
+ // Vulnerable, so propagate to all members of the equivalence class.
+ if (isUnderIndir)
+ {
+ hasOneVulnerable = true;
+ }
+ // Nothing to propagate.
+ else
+ {
+ continue;
+ }
+
+ // Second pass -- mark all are vulnerable.
+ assert(isUnderIndir);
+ for (UINT lclNum = assignGroup->bitVectGetFirst(); lclNum != (unsigned)-1;
+ lclNum = assignGroup->bitVectGetNext(lclNum))
+ {
+ lvaTable[lclNum].lvIsPtr = TRUE;
+ propagated->bitVectSet(lclNum);
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Equivalence assign group %s: ", isUnderIndir ? "isPtr " : "");
+ for (UINT lclNum = assignGroup->bitVectGetFirst(); lclNum != (unsigned)-1;
+ lclNum = assignGroup->bitVectGetNext(lclNum))
+ {
+ gtDispLclVar(lclNum, false);
+ printf(" ");
+ }
+ printf("\n");
+ }
+#endif
+ }
+
+ return hasOneVulnerable;
+}
+
+/*****************************************************************************
+ * gsParamsToShadows
+ * Copy each vulnerable param ptr or buffer to a local shadow copy and replace
+ * uses of the param by the shadow copy
+ */
+void Compiler::gsParamsToShadows()
+{
+ // Cache old count since we'll add new variables, and
+ // gsShadowVarInfo will not grow to accomodate the new ones.
+ UINT lvaOldCount = lvaCount;
+
+ // Create shadow copy for each param candidate
+ for (UINT lclNum = 0; lclNum < lvaOldCount; lclNum++)
+ {
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+ gsShadowVarInfo[lclNum].shadowCopy = NO_SHADOW_COPY;
+
+ // Only care about params whose values are on the stack
+ if (!ShadowParamVarInfo::mayNeedShadowCopy(varDsc))
+ {
+ continue;
+ }
+
+ if (!varDsc->lvIsPtr && !varDsc->lvIsUnsafeBuffer)
+ {
+ continue;
+ }
+
+ int shadowVar = lvaGrabTemp(false DEBUGARG("shadowVar"));
+ // Copy some info
+
+ var_types type = varTypeIsSmall(varDsc->TypeGet()) ? TYP_INT : varDsc->TypeGet();
+ lvaTable[shadowVar].lvType = type;
+
+#ifdef FEATURE_SIMD
+ lvaTable[shadowVar].lvSIMDType = varDsc->lvSIMDType;
+ lvaTable[shadowVar].lvUsedInSIMDIntrinsic = varDsc->lvUsedInSIMDIntrinsic;
+ if (varDsc->lvSIMDType)
+ {
+ lvaTable[shadowVar].lvBaseType = varDsc->lvBaseType;
+ }
+#endif
+ lvaTable[shadowVar].lvRegStruct = varDsc->lvRegStruct;
+
+ lvaTable[shadowVar].lvAddrExposed = varDsc->lvAddrExposed;
+ lvaTable[shadowVar].lvDoNotEnregister = varDsc->lvDoNotEnregister;
+#ifdef DEBUG
+ lvaTable[shadowVar].lvVMNeedsStackAddr = varDsc->lvVMNeedsStackAddr;
+ lvaTable[shadowVar].lvLiveInOutOfHndlr = varDsc->lvLiveInOutOfHndlr;
+ lvaTable[shadowVar].lvLclFieldExpr = varDsc->lvLclFieldExpr;
+ lvaTable[shadowVar].lvLiveAcrossUCall = varDsc->lvLiveAcrossUCall;
+#endif
+ lvaTable[shadowVar].lvVerTypeInfo = varDsc->lvVerTypeInfo;
+ lvaTable[shadowVar].lvGcLayout = varDsc->lvGcLayout;
+ lvaTable[shadowVar].lvIsUnsafeBuffer = varDsc->lvIsUnsafeBuffer;
+ lvaTable[shadowVar].lvIsPtr = varDsc->lvIsPtr;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Var V%02u is shadow param candidate. Shadow copy is V%02u.\n", lclNum, shadowVar);
+ }
+#endif
+
+ gsShadowVarInfo[lclNum].shadowCopy = shadowVar;
+ }
+
+ // Replace param uses with shadow copy
+ fgWalkAllTreesPre(gsReplaceShadowParams, (void*)this);
+
+ // Now insert code to copy the params to their shadow copy.
+ for (UINT lclNum = 0; lclNum < lvaOldCount; lclNum++)
+ {
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+
+ unsigned shadowVar = gsShadowVarInfo[lclNum].shadowCopy;
+ if (shadowVar == NO_SHADOW_COPY)
+ {
+ continue;
+ }
+
+ var_types type = lvaTable[shadowVar].TypeGet();
+
+ GenTreePtr src = gtNewLclvNode(lclNum, varDsc->TypeGet());
+ GenTreePtr dst = gtNewLclvNode(shadowVar, type);
+
+ src->gtFlags |= GTF_DONT_CSE;
+ dst->gtFlags |= GTF_DONT_CSE;
+
+ GenTreePtr opAssign = nullptr;
+ if (type == TYP_STRUCT)
+ {
+ CORINFO_CLASS_HANDLE clsHnd = varDsc->lvVerTypeInfo.GetClassHandle();
+
+ // We don't need unsafe value cls check here since we are copying the params and this flag
+ // would have been set on the original param before reaching here.
+ lvaSetStruct(shadowVar, clsHnd, false);
+
+ src = gtNewOperNode(GT_ADDR, TYP_BYREF, src);
+ dst = gtNewOperNode(GT_ADDR, TYP_BYREF, dst);
+
+ opAssign = gtNewCpObjNode(dst, src, clsHnd, false);
+ lvaTable[shadowVar].lvIsMultiRegArg = lvaTable[lclNum].lvIsMultiRegArg;
+ lvaTable[shadowVar].lvIsMultiRegRet = lvaTable[lclNum].lvIsMultiRegRet;
+ }
+ else
+ {
+ opAssign = gtNewAssignNode(dst, src);
+ }
+ fgEnsureFirstBBisScratch();
+ (void)fgInsertStmtAtBeg(fgFirstBB, fgMorphTree(opAssign));
+ }
+
+ // If the method has "Jmp CalleeMethod", then we need to copy shadow params back to original
+ // params before "jmp" to CalleeMethod.
+ if (compJmpOpUsed)
+ {
+ // There could be more than one basic block ending with a "Jmp" type tail call.
+ // We would have to insert assignments in all such blocks, just before GT_JMP stmnt.
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (block->bbJumpKind != BBJ_RETURN)
+ {
+ continue;
+ }
+
+ if ((block->bbFlags & BBF_HAS_JMP) == 0)
+ {
+ continue;
+ }
+
+ for (UINT lclNum = 0; lclNum < info.compArgsCount; lclNum++)
+ {
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+
+ unsigned shadowVar = gsShadowVarInfo[lclNum].shadowCopy;
+ if (shadowVar == NO_SHADOW_COPY)
+ {
+ continue;
+ }
+
+ GenTreePtr src = gtNewLclvNode(shadowVar, lvaTable[shadowVar].TypeGet());
+ GenTreePtr dst = gtNewLclvNode(lclNum, varDsc->TypeGet());
+
+ src->gtFlags |= GTF_DONT_CSE;
+ dst->gtFlags |= GTF_DONT_CSE;
+
+ GenTreePtr opAssign = nullptr;
+ if (varDsc->TypeGet() == TYP_STRUCT)
+ {
+ CORINFO_CLASS_HANDLE clsHnd = varDsc->lvVerTypeInfo.GetClassHandle();
+ src = gtNewOperNode(GT_ADDR, TYP_BYREF, src);
+ dst = gtNewOperNode(GT_ADDR, TYP_BYREF, dst);
+
+ opAssign = gtNewCpObjNode(dst, src, clsHnd, false);
+ }
+ else
+ {
+ opAssign = gtNewAssignNode(dst, src);
+ }
+
+ (void)fgInsertStmtNearEnd(block, fgMorphTree(opAssign));
+ }
+ }
+ }
+}
+
+/*****************************************************************************
+ * gsReplaceShadowParams (tree-walk call-back)
+ * Replace all vulnerable param uses by it's shadow copy.
+ */
+
+Compiler::fgWalkResult Compiler::gsReplaceShadowParams(GenTreePtr* pTree, fgWalkData* data)
+{
+ Compiler* comp = data->compiler;
+ GenTreePtr tree = *pTree;
+ GenTreePtr asg = nullptr;
+
+ if (tree->gtOper == GT_ASG)
+ {
+ asg = tree; // "asg" is the assignment tree.
+ tree = tree->gtOp.gtOp1; // "tree" is the local var tree at the left-hand size of the assignment.
+ }
+
+ if (tree->gtOper == GT_LCL_VAR || tree->gtOper == GT_LCL_FLD)
+ {
+ UINT paramNum = tree->gtLclVarCommon.gtLclNum;
+
+ if (!ShadowParamVarInfo::mayNeedShadowCopy(&comp->lvaTable[paramNum]) ||
+ comp->gsShadowVarInfo[paramNum].shadowCopy == NO_SHADOW_COPY)
+ {
+ return WALK_CONTINUE;
+ }
+
+ tree->gtLclVarCommon.SetLclNum(comp->gsShadowVarInfo[paramNum].shadowCopy);
+
+ // In gsParamsToShadows(), we create a shadow var of TYP_INT for every small type param.
+ // Make sure we update the type of the local var tree as well.
+ if (varTypeIsSmall(comp->lvaTable[paramNum].TypeGet()))
+ {
+ tree->gtType = TYP_INT;
+ if (asg)
+ {
+ // If this is an assignment tree, propagate the type to it as well.
+ asg->gtType = TYP_INT;
+ }
+ }
+ }
+
+ return WALK_CONTINUE;
+}