-
Notifications
You must be signed in to change notification settings - Fork 1.7k
C++: Assume modelled functions always override buffers by default #15633
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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
625c47f
24a63ae
7e9bf2a
499ab08
9b2019d
497592a
57c1bf5
b407c86
be54a41
aca3970
671904d
350d5bf
0bf29f0
c7ee5b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,11 @@ private import DataFlowUtil | |
private import DataFlowImplCommon as DataFlowImplCommon | ||
private import semmle.code.cpp.models.interfaces.Allocation as Alloc | ||
private import semmle.code.cpp.models.interfaces.DataFlow as DataFlow | ||
private import semmle.code.cpp.models.interfaces.Taint as Taint | ||
private import semmle.code.cpp.models.interfaces.PartialFlow as PartialFlow | ||
private import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs as FIO | ||
private import semmle.code.cpp.ir.internal.IRCppLanguage | ||
private import semmle.code.cpp.ir.dataflow.internal.ModelUtil | ||
private import DataFlowPrivate | ||
private import ssa0.SsaInternals as SsaInternals0 | ||
import SsaInternalsCommon | ||
|
@@ -138,12 +142,11 @@ private newtype TDefOrUseImpl = | |
isIteratorUse(container, iteratorAddress, _, indirectionIndex) | ||
} or | ||
TFinalParameterUse(Parameter p, int indirectionIndex) { | ||
// Avoid creating parameter nodes if there is no definitions of the variable other than the initializaion. | ||
exists(SsaInternals0::Def def | | ||
def.getSourceVariable().getBaseVariable().(BaseIRVariable).getIRVariable().getAst() = p and | ||
not def.getValue().asInstruction() instanceof InitializeParameterInstruction and | ||
underlyingTypeIsModifiableAt(p.getUnderlyingType(), indirectionIndex) | ||
) | ||
underlyingTypeIsModifiableAt(p.getUnderlyingType(), indirectionIndex) and | ||
// Only create an SSA read for the final use of a parameter if there's | ||
// actually a body of the enclosing function. If there's no function body | ||
// then we'll never need to flow out of the function anyway. | ||
p.getFunction().hasDefinition() | ||
} | ||
|
||
private predicate isGlobalUse( | ||
|
@@ -796,10 +799,58 @@ private Node getAPriorDefinition(SsaDefOrUse defOrUse) { | |
) | ||
} | ||
|
||
private predicate inOut(FIO::FunctionInput input, FIO::FunctionOutput output) { | ||
exists(int indirectionIndex | | ||
input.isQualifierObject(indirectionIndex) and | ||
output.isQualifierObject(indirectionIndex) | ||
or | ||
exists(int i | | ||
input.isParameterDeref(i, indirectionIndex) and | ||
output.isParameterDeref(i, indirectionIndex) | ||
) | ||
) | ||
} | ||
|
||
/** | ||
* Holds if there should not be use-use flow out of `n`. That is, `n` is | ||
* an out-barrier to use-use flow. This includes: | ||
* | ||
* - an input to a call that would be assumed to have use-use flow to the same | ||
* argument as an output, but this flow should be blocked because the | ||
* function is modeled with another flow to that output (for example the | ||
* first argument of `strcpy`). | ||
* - a conversion that flows to such an input. | ||
*/ | ||
private predicate modeledFlowBarrier(Node n) { | ||
exists( | ||
FIO::FunctionInput input, FIO::FunctionOutput output, CallInstruction call, | ||
PartialFlow::PartialFlowFunction partialFlowFunc | ||
| | ||
n = callInput(call, input) and | ||
inOut(input, output) and | ||
exists(callOutput(call, output)) and | ||
partialFlowFunc = call.getStaticCallTarget() and | ||
not partialFlowFunc.isPartialWrite(output) | ||
| | ||
call.getStaticCallTarget().(DataFlow::DataFlowFunction).hasDataFlow(_, output) | ||
or | ||
call.getStaticCallTarget().(Taint::TaintFunction).hasTaintFlow(_, output) | ||
) | ||
or | ||
exists(Operand operand, Instruction instr, Node n0, int indirectionIndex | | ||
modeledFlowBarrier(n0) and | ||
nodeHasInstruction(n0, instr, indirectionIndex) and | ||
conversionFlow(operand, instr, false, _) and | ||
nodeHasOperand(n, operand, indirectionIndex) | ||
) | ||
} | ||
|
||
/** Holds if there is def-use or use-use flow from `nodeFrom` to `nodeTo`. */ | ||
predicate ssaFlow(Node nodeFrom, Node nodeTo) { | ||
exists(Node nFrom, boolean uncertain, SsaDefOrUse defOrUse | | ||
ssaFlowImpl(defOrUse, nFrom, nodeTo, uncertain) and nodeFrom != nodeTo | ||
ssaFlowImpl(defOrUse, nFrom, nodeTo, uncertain) and | ||
not modeledFlowBarrier(nFrom) and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if someone naively models a flow from an argument to itself, without using the partial flow model? Will the model inexplicably fail? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing will inexplicably fail. In fact, nothing will fail at all. Let's assume you have this snippet: void test() {
char* p = taint(); // (1)
foo(p); // (2)
sink(p); // (3)
} Let's consider three scenarios:
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input.isParameterDeref(0) and
output.isParameterDeref(0)
}
override predicate isPartialWrite(FunctionOutput output) { output.isParameterDeref(0) } The behavior for each case will be: You have no model for
|
||
nodeFrom != nodeTo | ||
| | ||
if uncertain = true then nodeFrom = [nFrom, getAPriorDefinition(defOrUse)] else nodeFrom = nFrom | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -118,6 +118,8 @@ private class StdSequenceContainerData extends TaintFunction { | |
input.isReturnValueDeref() and | ||
output.isQualifierObject() | ||
} | ||
|
||
override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure we don't want this to be the default behaviour for qualifiers. I get that, in principle, a qualifier isn't all that different to an argument (at least an "inout" style argument), but typical usage is different and I would expect a partial write to be the norm??? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be something to think about, but it doesn't need to hold up the PR necessarily. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, a partial write is probably the norm. There are obvious outliers such as std::string::clear, but I would also expect it to be the norm. I'm not sure how to structure this best, though. Since we're now "blocking by default" and have thus added a predicate to "disable blocking" there's no now predicate to override for "enabling blocking". If we should make writes to the qualifier non-blocking by default we should also have a "enabling blocking" predicate. I supposed that could simply be a matter of adding an overridable predicate (with a sensible default) to But I'd like to leave that as a follow-up like you suggested. |
||
} | ||
|
||
/** | ||
|
@@ -147,6 +149,8 @@ private class StdSequenceContainerPushModel extends StdSequenceContainerPush, Ta | |
input.isParameterDeref(0) and | ||
output.isQualifierObject() | ||
} | ||
|
||
override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() } | ||
} | ||
|
||
/** | ||
|
@@ -207,6 +211,8 @@ private class StdSequenceContainerInsertModel extends StdSequenceContainerInsert | |
output.isReturnValue() | ||
) | ||
} | ||
|
||
override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() } | ||
} | ||
|
||
/** | ||
|
@@ -263,6 +269,8 @@ private class StdSequenceContainerAt extends TaintFunction { | |
input.isReturnValueDeref() and | ||
output.isQualifierObject() | ||
} | ||
|
||
override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() } | ||
} | ||
|
||
/** | ||
|
@@ -297,6 +305,8 @@ private class StdSequenceEmplaceModel extends StdSequenceEmplace, TaintFunction | |
output.isReturnValue() | ||
) | ||
} | ||
|
||
override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() } | ||
} | ||
|
||
/** | ||
|
@@ -335,6 +345,8 @@ private class StdSequenceEmplaceBackModel extends StdSequenceEmplaceBack, TaintF | |
input.isParameterDeref([0 .. this.getNumberOfParameters() - 1]) and | ||
output.isQualifierObject() | ||
} | ||
|
||
override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() } | ||
} | ||
|
||
/** | ||
|
Uh oh!
There was an error while loading. Please reload this page.