Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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

Merged
merged 14 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,12 @@ private newtype TIRDataFlowNode =
TFinalParameterNode(Parameter p, int indirectionIndex) {
exists(Ssa::FinalParameterUse use |
use.getParameter() = p and
use.getIndirectionIndex() = indirectionIndex and
parameterIsRedefined(p)
use.getIndirectionIndex() = indirectionIndex
)
} or
TFinalGlobalValue(Ssa::GlobalUse globalUse) or
TInitialGlobalValue(Ssa::GlobalDef globalUse)

/**
* Holds if the value of `*p` (or `**p`, `***p`, etc.) is redefined somewhere in the body
* of the enclosing function of `p`.
*
* Only parameters satisfying this predicate will generate a `FinalParameterNode` transferring
* flow out of the function.
*/
private predicate parameterIsRedefined(Parameter p) {
exists(Ssa::Def def |
def.getSourceVariable().getBaseVariable().(Ssa::BaseIRVariable).getIRVariable().getAst() = p and
def.getIndirectionIndex() = 0 and
def.getIndirection() > 1 and
not def.getValue().asInstruction() instanceof InitializeParameterInstruction
)
}

/**
* An operand that is defined by a `FieldAddressInstruction`.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
geoffw0 marked this conversation as resolved.
Show resolved Hide resolved
|
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

@MathiasVP MathiasVP Feb 22, 2024

Choose a reason for hiding this comment

The 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:

  1. You have no model for foo
  2. You have a dataflow model for foo that looks like:
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
  input.isParameterDeref(0) and
  output.isParameterDeref(0)
}
  1. You have a dataflow model for foo that looks like the above, but also has:
override predicate isPartialWrite(FunctionOutput output) { output.isParameterDeref(0) }

The behavior for each case will be:

You have no model for foo

There will be flow from taint() to p (in // (2)), and from p (in // (2)) to p (in // (3)). See here:

Screenshot 2024-02-22 at 14 47 50

You have a dataflow model for foo, but no isPartialWrite override

There will be flow from taint() to p (in // (2)), and from p (in // (2)) to the output argument of foo, and from the output argument of foo to p (in // (3). See here:

Screenshot 2024-02-22 at 14 50 17

That is, now that the model for foo implicitly blocks flow it means that flow will have to go through foo in order to reach sink.

You have a dataflow model for foo, and you also override isPartialWrite.

Since the flow is now specified as a partial write it means you get both of the flows from before:

Screenshot 2024-02-22 at 14 51 48

Copy link
Contributor Author

@MathiasVP MathiasVP Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should add: The paths above have some duplication (i.e., there are two *call to taint nodes along the path) since I disable any kind of path compression in my example.

nodeFrom != nodeTo
|
if uncertain = true then nodeFrom = [nFrom, getAPriorDefinition(defOrUse)] else nodeFrom = nFrom
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ private class GetDelimFunction extends TaintFunction, AliasFunction, SideEffectF
i.isParameter(3) and o.isParameterDeref(0)
}

override predicate isPartialWrite(FunctionOutput o) { o.isParameterDeref(3) }

override predicate parameterNeverEscapes(int index) { index = [0, 1, 3] }

override predicate parameterEscapesOnlyViaReturn(int index) { none() }
Expand Down
2 changes: 2 additions & 0 deletions 2 cpp/ql/lib/semmle/code/cpp/models/implementations/Gets.qll
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ private class FgetsFunction extends DataFlowFunction, TaintFunction, ArrayFuncti
output.isReturnValue()
}

override predicate isPartialWrite(FunctionOutput output) { output.isParameterDeref(2) }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(2) and
output.isParameterDeref(0)
Expand Down
2 changes: 2 additions & 0 deletions 2 cpp/ql/lib/semmle/code/cpp/models/implementations/Inet.qll
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ private class InetAton extends TaintFunction, ArrayFunction {
output.isParameterDeref(1)
}

override predicate isPartialWrite(FunctionOutput output) { output.isParameterDeref(1) }

override predicate hasArrayInput(int bufParam) { bufParam = 0 }

override predicate hasArrayOutput(int bufParam) { bufParam = 1 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ private class StdSequenceContainerData extends TaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
Copy link
Contributor

Choose a reason for hiding this comment

The 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???

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 PartialFlowFunction that says that writes to qualifiers are always non-blocking.

But I'd like to leave that as a follow-up like you suggested.

}

/**
Expand Down Expand Up @@ -147,6 +149,8 @@ private class StdSequenceContainerPushModel extends StdSequenceContainerPush, Ta
input.isParameterDeref(0) and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -207,6 +211,8 @@ private class StdSequenceContainerInsertModel extends StdSequenceContainerInsert
output.isReturnValue()
)
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -263,6 +269,8 @@ private class StdSequenceContainerAt extends TaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -297,6 +305,8 @@ private class StdSequenceEmplaceModel extends StdSequenceEmplace, TaintFunction
output.isReturnValue()
)
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -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() }
}

/**
Expand Down
11 changes: 11 additions & 0 deletions 11 cpp/ql/lib/semmle/code/cpp/models/implementations/StdMap.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import semmle.code.cpp.models.interfaces.Taint
import semmle.code.cpp.models.interfaces.DataFlow
import semmle.code.cpp.models.interfaces.Iterator

/**
Expand Down Expand Up @@ -53,6 +54,8 @@ private class StdMapInsert extends TaintFunction {
output.isReturnValue()
)
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -75,6 +78,8 @@ private class StdMapEmplace extends TaintFunction {
input.isQualifierObject() and
output.isReturnValue()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -102,6 +107,8 @@ private class StdMapTryEmplace extends TaintFunction {
input.isQualifierObject() and
output.isReturnValue()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -115,6 +122,8 @@ private class StdMapMerge extends TaintFunction {
input.isParameterDeref(0) and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -132,6 +141,8 @@ private class StdMapAt extends TaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private class StdSetInsert extends TaintFunction {
output.isReturnValue()
)
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -82,6 +84,8 @@ private class StdSetEmplace extends TaintFunction {
input.isQualifierObject() and
output.isReturnValue()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -95,6 +99,8 @@ private class StdSetMerge extends TaintFunction {
input.isParameterDeref(0) and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down
26 changes: 26 additions & 0 deletions 26 cpp/ql/lib/semmle/code/cpp/models/implementations/StdString.qll
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ private class StdStringDataModel extends StdStringData, StdStringTaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -142,6 +144,8 @@ private class StdStringPush extends StdStringTaintFunction {
input.isParameter(0) and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -204,6 +208,8 @@ private class StdStringAppend extends StdStringTaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -237,6 +243,8 @@ private class StdStringInsert extends StdStringTaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -305,6 +313,8 @@ private class StdStringAt extends StdStringTaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand Down Expand Up @@ -338,6 +348,8 @@ private class StdIStreamIn extends DataFlowFunction, TaintFunction {
input.isReturnValueDeref() and
output.isQualifierObject()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }
}

/**
Expand All @@ -358,6 +370,8 @@ private class StdIStreamInNonMember extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from first parameter to second parameter
input.isParameterDeref(0) and
Expand Down Expand Up @@ -403,6 +417,8 @@ private class StdIStreamRead extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to first parameter
input.isQualifierObject() and
Expand Down Expand Up @@ -442,6 +458,8 @@ private class StdIStreamPutBack extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from first parameter (value or pointer) to qualifier
input.isParameter(0) and
Expand Down Expand Up @@ -478,6 +496,8 @@ private class StdIStreamGetLine extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to first parameter
input.isQualifierObject() and
Expand Down Expand Up @@ -540,6 +560,8 @@ private class StdOStreamOut extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from first parameter (value or pointer) to qualifier
input.isParameter(0) and
Expand Down Expand Up @@ -579,6 +601,8 @@ private class StdOStreamOutNonMember extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isParameterDeref(0) }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from second parameter to first parameter
input.isParameterDeref(1) and
Expand Down Expand Up @@ -672,6 +696,8 @@ private class StdStreamFunction extends DataFlowFunction, TaintFunction {
output.isReturnValueDeref()
}

override predicate isPartialWrite(FunctionOutput output) { output.isQualifierObject() }

override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// reverse flow from returned reference to the qualifier
input.isReturnValueDeref() and
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.