Merge branch 'main' into atorralba/promote-ognl-injection

This commit is contained in:
Tony Torralba
2021-08-02 14:06:45 +02:00
158 changed files with 4115 additions and 1840 deletions

View File

@@ -1,4 +0,0 @@
lgtm,codescanning
* The `StackVariableReachability` library now ignores some paths that contain an infeasible combination
of conditionals. These improvements primarily affect the queries `cpp/uninitialized-local` and
`cpp/use-after-free`.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The "Cleartext storage of sensitive information in file" (cpp/cleartext-storage-file) query now uses dataflow to produce additional results.
* Heuristics in the SensitiveExprs.qll library have been improved, making the "Cleartext storage of sensitive information in file" (cpp/cleartext-storage-file), "Cleartext storage of sensitive information in buffer" (cpp/cleartext-storage-buffer) and "Cleartext storage of sensitive information in an SQLite" (cpp/cleartext-storage-database) queries more accurate.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Improvements have been made to the `cpp/toctou-race-condition` query, both to find more correct results and fewer false positive results.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Virtual function specifiers are now accessible via the new predicates on `Function` (`.isDeclaredVirtual`, `.isOverride`, and `.isFinal`).

View File

@@ -41,7 +41,7 @@ DeclStmt declWithNoInit(LocalVariable v) {
result.getADeclaration() = v and
not exists(v.getInitializer()) and
/* The type of the variable is not stack-allocated. */
not allocatedType(v.getType())
exists(Type t | t = v.getType() | not allocatedType(t))
}
class UninitialisedLocalReachability extends StackVariableReachability {

View File

@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 7.5
* @precision medium
* @precision high
* @id cpp/cleartext-storage-file
* @tags security
* external/cwe/cwe-313
@@ -14,10 +14,40 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.security.FileWrite
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
from FileWrite w, SensitiveExpr source, Expr dest
/**
* An operation on a filename.
*/
predicate filenameOperation(FunctionCall op, Expr path) {
exists(string name | name = op.getTarget().getName() |
name =
[
"remove", "unlink", "rmdir", "rename", "fopen", "open", "freopen", "_open", "_wopen",
"_wfopen", "_fsopen", "_wfsopen", "chmod", "chown", "stat", "lstat", "fstat", "access",
"_access", "_waccess", "_access_s", "_waccess_s"
] and
path = op.getArgument(0)
or
name = ["fopen_s", "wfopen_s", "rename"] and
path = op.getArgument(1)
)
}
predicate isFileName(GVN gvn) {
exists(FunctionCall op, Expr path |
filenameOperation(op, path) and
gvn = globalValueNumber(path)
)
}
from FileWrite w, SensitiveExpr source, Expr mid, Expr dest
where
source = w.getASource() and
dest = w.getDest()
DataFlow::localFlow(DataFlow::exprNode(source), DataFlow::exprNode(mid)) and
mid = w.getASource() and
dest = w.getDest() and
not isFileName(globalValueNumber(source)) and // file names are not passwords
not exists(string convChar | convChar = w.getSourceConvChar(mid) | not convChar = ["s", "S"]) // ignore things written with other conversion characters
select w, "This write into file '" + dest.toString() + "' may contain unencrypted data from $@",
source, "this source."

View File

@@ -6,7 +6,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 7.7
* @precision medium
* @precision high
* @id cpp/toctou-race-condition
* @tags security
* external/cwe/cwe-367
@@ -26,28 +26,29 @@ import semmle.code.cpp.controlflow.Guards
*/
FunctionCall filenameOperation(Expr path) {
exists(string name | name = result.getTarget().getName() |
(
name = "remove" or
name = "unlink" or
name = "rmdir" or
name = "rename" or
name = "chmod" or
name = "chown" or
name = "fopen" or
name = "open" or
name = "freopen" or
name = "_open" or
name = "_wopen" or
name = "_wfopen"
) and
name =
[
"remove", "unlink", "rmdir", "rename", "fopen", "open", "freopen", "_open", "_wopen",
"_wfopen", "_fsopen", "_wfsopen"
] and
result.getArgument(0) = path
or
(
name = "fopen_s" or
name = "wfopen_s"
) and
name = ["fopen_s", "wfopen_s", "rename"] and
result.getArgument(1) = path
)
or
result = sensitiveFilenameOperation(path)
}
/**
* An operation on a filename that is likely to modify the security properties
* of the corresponding file and may return an indication of success.
*/
FunctionCall sensitiveFilenameOperation(Expr path) {
exists(string name | name = result.getTarget().getName() |
name = ["chmod", "chown"] and
result.getArgument(0) = path
)
}
/**
@@ -56,11 +57,7 @@ FunctionCall filenameOperation(Expr path) {
*/
FunctionCall accessCheck(Expr path) {
exists(string name | name = result.getTarget().getName() |
name = "access" or
name = "_access" or
name = "_waccess" or
name = "_access_s" or
name = "_waccess_s"
name = ["access", "_access", "_waccess", "_access_s", "_waccess_s"]
) and
path = result.getArgument(0)
}
@@ -72,9 +69,7 @@ FunctionCall accessCheck(Expr path) {
*/
FunctionCall stat(Expr path, Expr buf) {
exists(string name | name = result.getTarget().getName() |
name = "stat" or
name = "lstat" or
name = "fstat" or
name = ["stat", "lstat", "fstat"] or
name.matches("\\_stat%") or
name.matches("\\_wstat%")
) and
@@ -98,29 +93,36 @@ from Expr check, Expr checkPath, FunctionCall use, Expr usePath
where
// `check` looks like a check on a filename
(
// either:
// an access check
check = accessCheck(checkPath)
or
// a stat
check = stat(checkPath, _)
(
// either:
// an access check
check = accessCheck(checkPath)
or
// a stat
check = stat(checkPath, _)
or
// access to a member variable on the stat buf
// (morally, this should be a use-use pair, but it seems unlikely
// that this variable will get reused in practice)
exists(Expr call, Expr e, Variable v |
call = stat(checkPath, e) and
e.getAChild*().(VariableAccess).getTarget() = v and
check.(VariableAccess).getTarget() = v and
not e.getAChild*() = check // the call that writes to the pointer is not where the pointer is checked.
)
) and
// `op` looks like an operation on a filename
use = filenameOperation(usePath)
or
// another filename operation (null pointers can indicate errors)
check = filenameOperation(checkPath)
or
// access to a member variable on the stat buf
// (morally, this should be a use-use pair, but it seems unlikely
// that this variable will get reused in practice)
exists(Variable buf | exists(stat(checkPath, buf.getAnAccess())) |
check.(VariableAccess).getQualifier() = buf.getAnAccess()
)
check = filenameOperation(checkPath) and
// `op` looks like a sensitive operation on a filename
use = sensitiveFilenameOperation(usePath)
) and
// `checkPath` and `usePath` refer to the same SSA variable
exists(SsaDefinition def, StackVariable v |
def.getAUse(v) = checkPath and def.getAUse(v) = usePath
) and
// `op` looks like an operation on a filename
use = filenameOperation(usePath) and
// the return value of `check` is used (possibly with one step of
// variable indirection) in a guard which controls `use`
exists(GuardCondition guard | referenceTo(check, guard.getAChild*()) |

View File

@@ -82,9 +82,23 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
/** Holds if this function is inline. */
predicate isInline() { this.hasSpecifier("inline") }
/** Holds if this function is virtual. */
/**
* Holds if this function is virtual.
*
* Unlike `isDeclaredVirtual()`, `isVirtual()` holds even if the function
* is not explicitly declared with the `virtual` specifier.
*/
predicate isVirtual() { this.hasSpecifier("virtual") }
/** Holds if this function is declared with the `virtual` specifier. */
predicate isDeclaredVirtual() { this.hasSpecifier("declared_virtual") }
/** Holds if this function is declared with the `override` specifier. */
predicate isOverride() { this.hasSpecifier("override") }
/** Holds if this function is declared with the `final` specifier. */
predicate isFinal() { this.hasSpecifier("final") }
/**
* Holds if this function is deleted.
* This may be because it was explicitly deleted with an `= delete`

View File

@@ -208,7 +208,7 @@ private predicate bbSuccessorEntryReachesDefOrUse(
boolean skipsFirstLoopAlwaysTrueUponEntry
) {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant0(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
|
bbEntryReachesDefOrUseLocally(succ, v, defOrUse) and

View File

@@ -3,250 +3,7 @@
* reachability involving stack variables.
*/
private import semmle.code.cpp.controlflow.Guards
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
/** A `GuardCondition` which appear in a control-flow path to a sink. */
abstract private class LogicalGuardCondition extends GuardCondition {
LogicalGuardCondition() {
// Either the `GuardCondition` is part of the path from a source to a sink
revBbSuccessorEntryReaches0(_, this.getBasicBlock(), _, _, _)
or
// or it controls the basic block that contains the source node.
this.controls(any(BasicBlock bb | fwdBbEntryReachesLocally(bb, _, _, _)), _)
}
/**
* Holds if the truth of this logical expression having value `wholeIsTrue`
* implies that the truth of the child expression `part` has truth value `partIsTrue`.
*/
abstract predicate impliesCondition(
LogicalGuardCondition e, boolean testIsTrue, boolean condIsTrue
);
}
private class BinaryLogicalGuardCondition extends LogicalGuardCondition, BinaryLogicalOperation {
override predicate impliesCondition(
LogicalGuardCondition e, boolean testIsTrue, boolean condIsTrue
) {
this.impliesValue(e, testIsTrue, condIsTrue)
}
}
private class VariableGuardCondition extends LogicalGuardCondition, VariableAccess {
override predicate impliesCondition(
LogicalGuardCondition e, boolean testIsTrue, boolean condIsTrue
) {
this = e and
(
testIsTrue = true and condIsTrue = true
or
testIsTrue = false and condIsTrue = false
)
}
}
private class NotGuardCondition extends LogicalGuardCondition, NotExpr {
override predicate impliesCondition(
LogicalGuardCondition e, boolean testIsTrue, boolean condIsTrue
) {
e = this.getOperand() and
(
testIsTrue = true and
condIsTrue = false
or
testIsTrue = false and
condIsTrue = true
)
}
}
private newtype TCondition =
MkCondition(LogicalGuardCondition guard, boolean testIsTrue) { testIsTrue = [false, true] }
private class Condition extends MkCondition {
boolean testIsTrue;
LogicalGuardCondition guard;
Condition() { this = MkCondition(guard, testIsTrue) }
/**
* Holds if this condition having the value `this.getTruthValue()` implies that `cond` has truth
* value `cond.getTruthValue()`.
*/
string toString() { result = guard.toString() + " == " + testIsTrue.toString() }
/** Gets the value of this `Condition`. */
boolean getTruthValue() { result = testIsTrue }
LogicalGuardCondition getCondition() { result = guard }
pragma[nomagic]
predicate impliesCondition(Condition cond) {
exists(LogicalGuardCondition other |
other = cond.getCondition() and
this.getCondition()
.impliesCondition(globalValueNumber(other).getAnExpr(),
pragma[only_bind_into](pragma[only_bind_out](testIsTrue)),
pragma[only_bind_into](pragma[only_bind_out](cond.getTruthValue())))
)
}
/** Gets the negated expression represented by this `Condition`, if any. */
private Condition negate() {
result.getCondition() = guard and
result.getTruthValue() = testIsTrue.booleanNot()
}
/**
* Holds if this condition having the value `this.getTruthValue()` implies that `cond` cannot have
* the truth value `cond.getTruthValue()`.
*/
final predicate refutesCondition(Condition cond) { this.impliesCondition(cond.negate()) }
/** Gets the `Location` of the expression that generated this `Condition`. */
Location getLocation() { result = guard.getLocation() }
}
/**
* Gets a `Condition` that controls `b`. That is, to enter `b` the condition must hold.
*/
private Condition getADirectCondition(BasicBlock b) {
result.getCondition().controls(b, result.getTruthValue())
}
/**
* Like the shared dataflow library, the reachability analysis is split into two stages:
* In the first stage, we compute an overapproximation of the possible control-flow paths where we don't
* reason about path conditions. This stage is split into phases: A forward phase (computed by the
* predicates prefixes with `fwd`), and a reverse phase (computed by the predicates prefixed with `rev`).
*
* The forward phease computes the set of control-flow nodes reachable from a given `source` and `v` such
* that `config.isSource(source, v)` holds.
*
* See the QLDoc on `revBbSuccessorEntryReaches0` for a description of what the reverse phase computes.
*/
private predicate fwdBbSuccessorEntryReaches0(
ControlFlowNode source, BasicBlock bb, SemanticStackVariable v,
boolean skipsFirstLoopAlwaysTrueUponEntry, StackVariableReachability config
) {
fwdBbEntryReachesLocally(bb, v, source, config) and
skipsFirstLoopAlwaysTrueUponEntry = false
or
exists(BasicBlock pred, boolean predSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant0(pred, bb, predSkipsFirstLoopAlwaysTrueUponEntry,
skipsFirstLoopAlwaysTrueUponEntry)
|
// Note we cannot filter out barriers at this point.
// See the comment in `revBbSuccessorEntryReaches0` for an explanation why,
fwdBbSuccessorEntryReaches0(source, pred, v, predSkipsFirstLoopAlwaysTrueUponEntry, config)
)
}
/**
* The second phase of the first stages computes, for each `source` and `v` pair such
* that `config.isSource(source, v)`, which sinks are reachable from that `(source, v)` pair.
*/
private predicate revBbSuccessorEntryReaches0(
ControlFlowNode source, BasicBlock bb, SemanticStackVariable v,
boolean skipsFirstLoopAlwaysTrueUponEntry, StackVariableReachability config
) {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
fwdBbSuccessorEntryReaches0(source, bb, v, skipsFirstLoopAlwaysTrueUponEntry, config) and
bbSuccessorEntryReachesLoopInvariant0(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
|
revBbEntryReachesLocally(succ, v, _, config) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
// Note: We cannot rule out a successor block that contain a barrier here (like we do later in
// `bbSuccessorEntryReaches`) as we might later discover that the only way to get through a piece of
// code is through that barrier, and we want to discover this in
// `bbSuccessorEntryReachesLoopInvariant`. As an example, consider this piece of code:
// ```
// if(b) { (1) source(); }
// (2) if(b) { (3) barrier(); }
// (4) sink();
// ```
// here, we want the successor relation to contain:
// 1 -> {2}, 2 -> {3, 4}
// since the second stage will deduce that the edge (2) -> (3) is unconditional (as b is always true
// if we start at `source()`), and so there is actually no path from (1) to (4) without going through
// a barrier.
revBbSuccessorEntryReaches0(source, succ, v, succSkipsFirstLoopAlwaysTrueUponEntry, config)
)
}
private predicate successorExitsLoop(BasicBlock pred, BasicBlock succ, Loop loop) {
pred.getASuccessor() = succ and
bbDominates(loop.getStmt(), pred) and
not bbDominates(loop.getStmt(), succ)
}
private predicate successorExitsFirstDisjunct(BasicBlock pred, BasicBlock succ) {
exists(LogicalOrExpr orExpr | orExpr instanceof GuardCondition |
pred.getAFalseSuccessor() = succ and
pred.contains(orExpr.getLeftOperand())
)
}
/**
* When we exit a loop, we filter out the conditions that arise from the loop's guard.
* To see why this is necessary, consider this example:
* ```
* (1) source();
* while (b) { (2) ... }
* (3) sink();
* ```
* If we keep all the conditions when we transition from (2) to (3) we learn that `b` is true at
* (3), but since we exited the loop we also learn that `b` is false at 3.
* Thus, when we transition from (2) to (3) we discard all those conditions that are true at (2),
* but NOT true at (3).
*/
private predicate isLoopCondition(LogicalGuardCondition cond, BasicBlock pred, BasicBlock bb) {
exists(Loop loop, boolean testIsTrue | successorExitsLoop(pred, bb, loop) |
// the resulting `Condition` holds inside the loop
cond.controls(pred, testIsTrue) and
// but not prior to the loop.
not cond.controls(loop.getBasicBlock(), testIsTrue)
)
}
/**
* When we leave the first disjunct we throw away the condition that says the the first disjunct is
* false. To see why this is necessary, consider this example:
* ```
* if((1) b1 || (2) b2) { (3) ... }
* ```
* it holds that `b1 == false` controls (2), and since (2) steps to (3) we learn that `b1 == false `
* holds at (3). So we filter out the conditions that we learn from leaving taking the false
* branch in a disjunction.
*/
private predicate isDisjunctionCondition(LogicalGuardCondition cond, BasicBlock pred, BasicBlock bb) {
exists(boolean testIsTrue | successorExitsFirstDisjunct(pred, bb) |
// the resulting `Condition` holds after evaluating the left-hand side
cond.controls(bb, testIsTrue) and
// but not before evaluating the left-hand side.
not cond.controls(pred, testIsTrue)
)
}
private predicate isLoopVariantCondition(LogicalGuardCondition cond, BasicBlock pred, BasicBlock bb) {
exists(Loop loop |
bb.getEnd() = loop.getCondition() and
pred.getASuccessor() = bb and
bbDominates(bb, pred) and
loopVariant(cond.getAChild*(), loop)
)
}
private predicate loopVariant(VariableAccess e, Loop loop) {
exists(SsaDefinition d | d.getAUse(e.getTarget()) = e |
d.getAnUltimateDefiningValue(e.getTarget()) = loop.getCondition().getAChild*() or
d.getAnUltimateDefiningValue(e.getTarget()).getEnclosingStmt().getParent*() = loop.getStmt() or
d.getAnUltimateDefiningValue(e.getTarget()) = loop.(ForStmt).getUpdate().getAChild*()
)
}
import cpp
/**
* A reachability analysis for control-flow nodes involving stack variables.
@@ -303,8 +60,7 @@ abstract class StackVariableReachability extends string {
* ```
*
* In addition to using a better performing implementation, this analysis
* accounts for loops where the condition is provably true upon entry, and discards paths that require
* an infeasible combination of guard conditions (for example, `if(b) { ... }` and `if(!b) { ... }`).
* accounts for loops where the condition is provably true upon entry.
*/
predicate reaches(ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink) {
/*
@@ -324,184 +80,46 @@ abstract class StackVariableReachability extends string {
j > i and
sink = bb.getNode(j) and
isSink(sink, v) and
not isBarrier(bb.getNode(pragma[only_bind_into]([i + 1 .. j - 1])), v)
not exists(int k | isBarrier(bb.getNode(k), v) | k in [i + 1 .. j - 1])
)
or
not exists(int k | isBarrier(bb.getNode(k), v) | k > i) and
bbSuccessorEntryReaches(source, bb, v, sink, _)
)
}
private Condition getASinkCondition(SemanticStackVariable v) {
exists(BasicBlock bb |
revBbEntryReachesLocally(bb, v, _, this) and
result.getCondition().controls(bb, result.getTruthValue())
)
}
private Condition getABarrierCondition(SemanticStackVariable v) {
exists(BasicBlock bb |
isBarrier(bb.getANode(), v) and
result.getCondition().controls(bb, result.getTruthValue())
)
}
/**
* Gets a condition with a known truth value in `bb` when the control-flow starts at the source
* node `source` and we're tracking reachability using variable `v` (that is,
* `this.isSource(source, v)` holds).
*
* This predicate is `pragma[noopt]` as it seems difficult to get the correct join order for the
* recursive case otherwise:
* revBbSuccessorEntryReaches0(bb) -> getASuccessor -> prev_delta ->
* revBbSuccessorEntryReaches0(pred) -> {isLoopCondition, isDisjunctionCondition, isLoopVariantCondition}
*/
pragma[noopt]
private Condition getACondition(ControlFlowNode source, SemanticStackVariable v, BasicBlock bb) {
revBbSuccessorEntryReaches0(source, bb, v, _, this) and
(
result = getADirectCondition(bb) and
(
exists(Condition c |
c = getASinkCondition(v) and
result.refutesCondition(c)
)
or
exists(Condition c |
c = getABarrierCondition(v) and
result.impliesCondition(c)
)
)
or
exists(BasicBlock pred |
pred.getASuccessor() = bb and
result = getACondition(source, v, pred) and
revBbSuccessorEntryReaches0(source, pred, v, _, this) and
exists(LogicalGuardCondition c | c = result.getCondition() |
not isLoopCondition(c, pred, bb) and
not isDisjunctionCondition(c, pred, bb) and
not isLoopVariantCondition(c, pred, bb)
)
)
)
}
pragma[nomagic]
private predicate bbSuccessorEntryReachesLoopInvariantSucc(
ControlFlowNode source, BasicBlock pred, SemanticStackVariable v, BasicBlock succ,
boolean predSkipsFirstLoopAlwaysTrueUponEntry
) {
revBbSuccessorEntryReaches0(source, pragma[only_bind_into](pred), v,
predSkipsFirstLoopAlwaysTrueUponEntry, this) and
pred.getASuccessor() = succ
}
pragma[nomagic]
private predicate bbSuccessorEntryReachesLoopInvariantCand(
ControlFlowNode source, BasicBlock pred, SemanticStackVariable v, BasicBlock succ,
boolean predSkipsFirstLoopAlwaysTrueUponEntry, boolean succSkipsFirstLoopAlwaysTrueUponEntry
) {
bbSuccessorEntryReachesLoopInvariantSucc(source, pragma[only_bind_into](pred), v, succ,
predSkipsFirstLoopAlwaysTrueUponEntry) and
bbSuccessorEntryReachesLoopInvariant0(pred, succ, predSkipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
}
/**
* Holds if `pred`, `succ`, `predSkipsFirstLoopAlwaysTrueUponEntry` and
* `succSkipsFirstLoopAlwaysTrueUponEntry` satisfy the loop invariants specified in the QLDoc
* for `bbSuccessorEntryReachesLoopInvariant0`.
*
* In addition, this predicate:
* 1. Rules out successor blocks that are unreachable due to contradictory path conditions.
* 2. Refines the successor relation when the edge `pred -> succ` is a conditional edge whose truth
* value is known.
*/
pragma[nomagic]
private predicate bbSuccessorEntryReachesLoopInvariant(
ControlFlowNode source, BasicBlock pred, SemanticStackVariable v, BasicBlock succ,
boolean predSkipsFirstLoopAlwaysTrueUponEntry, boolean succSkipsFirstLoopAlwaysTrueUponEntry
) {
bbSuccessorEntryReachesLoopInvariantCand(source, pred, v, succ,
predSkipsFirstLoopAlwaysTrueUponEntry, succSkipsFirstLoopAlwaysTrueUponEntry) and
not exists(Condition cond, Condition direct |
cond = getACondition(source, v, pred) and
direct = pragma[only_bind_out](getADirectCondition(succ)) and
cond.refutesCondition(direct)
) and
(
// If we picked the successor edge corresponding to a condition being true, there must not be
// another path condition that refutes that the condition is true.
not exists(Condition cond | cond = getACondition(source, v, pred) |
succ = pred.getATrueSuccessor() and
cond.refutesCondition(pragma[only_bind_out](MkCondition(pred.getEnd(), true)))
) and
// If we picked the successor edge corresponding to a condition being false, there must not be
// another path condition that refutes that the condition is false.
not exists(Condition cond | cond = getACondition(source, v, pred) |
succ = pred.getAFalseSuccessor() and
cond.refutesCondition(pragma[only_bind_out](MkCondition(pred.getEnd(), false)))
)
bbSuccessorEntryReaches(bb, v, sink, _)
)
}
private predicate bbSuccessorEntryReaches(
ControlFlowNode source, BasicBlock bb, SemanticStackVariable v, ControlFlowNode node,
BasicBlock bb, SemanticStackVariable v, ControlFlowNode node,
boolean skipsFirstLoopAlwaysTrueUponEntry
) {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant(source, bb, v, succ, skipsFirstLoopAlwaysTrueUponEntry,
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
|
revBbEntryReachesLocally(succ, v, node, this) and
bbEntryReachesLocally(succ, v, node) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
bbSuccessorEntryReachesLoopInvariant(source, bb, v, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry) and
not isBarrier(pragma[only_bind_out](succ.getANode()), v) and
pragma[only_bind_into](this)
.bbSuccessorEntryReaches(source, succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
not isBarrier(succ.getNode(_), v) and
bbSuccessorEntryReaches(succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
)
}
}
private predicate fwdBbEntryReachesLocally(
BasicBlock bb, SemanticStackVariable v, ControlFlowNode node, StackVariableReachability config
) {
exists(int n |
node = bb.getNode(n) and
config.isSource(node, v) and
(
not exists(lastBarrierIndexIn(bb, v, config))
private predicate bbEntryReachesLocally(
BasicBlock bb, SemanticStackVariable v, ControlFlowNode node
) {
exists(int n |
node = bb.getNode(n) and
isSink(node, v)
|
not exists(this.firstBarrierIndexIn(bb, v))
or
lastBarrierIndexIn(bb, v, config) <= n
n <= this.firstBarrierIndexIn(bb, v)
)
)
}
}
private predicate revBbEntryReachesLocally(
BasicBlock bb, SemanticStackVariable v, ControlFlowNode node, StackVariableReachability config
) {
exists(int n |
node = bb.getNode(n) and
config.isSink(node, v)
|
not exists(firstBarrierIndexIn(bb, v, config))
or
n <= firstBarrierIndexIn(bb, v, config)
)
}
private int firstBarrierIndexIn(
BasicBlock bb, SemanticStackVariable v, StackVariableReachability config
) {
result = min(int m | config.isBarrier(bb.getNode(m), v))
}
private int lastBarrierIndexIn(
BasicBlock bb, SemanticStackVariable v, StackVariableReachability config
) {
result = max(int m | config.isBarrier(bb.getNode(m), v))
private int firstBarrierIndexIn(BasicBlock bb, SemanticStackVariable v) {
result = min(int m | isBarrier(bb.getNode(m), v))
}
}
/**
@@ -564,7 +182,7 @@ private predicate bbLoopConditionAlwaysTrueUponEntrySuccessor(
* is provably true upon entry, then `succ` is not allowed to skip
* that loop (`succSkipsFirstLoopAlwaysTrueUponEntry = false`).
*/
predicate bbSuccessorEntryReachesLoopInvariant0(
predicate bbSuccessorEntryReachesLoopInvariant(
BasicBlock pred, BasicBlock succ, boolean predSkipsFirstLoopAlwaysTrueUponEntry,
boolean succSkipsFirstLoopAlwaysTrueUponEntry
) {
@@ -678,52 +296,10 @@ abstract class StackVariableReachabilityWithReassignment extends StackVariableRe
)
}
private predicate bbSuccessorEntryReaches(
BasicBlock bb, SemanticStackVariable v, ControlFlowNode node,
boolean skipsFirstLoopAlwaysTrueUponEntry
) {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant0(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry)
|
revBbEntryReachesLocally(succ, v, node, this) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
not isBarrier(succ.getNode(_), v) and
bbSuccessorEntryReaches(succ, v, node, succSkipsFirstLoopAlwaysTrueUponEntry)
)
}
private predicate reaches0(ControlFlowNode source, SemanticStackVariable v, ControlFlowNode sink) {
/*
* Implementation detail: the predicates in this class are a generalization of
* those in DefinitionsAndUses.qll, and should be kept in sync.
*
* Unfortunately, caching of abstract predicates does not work well, so the
* predicates in DefinitionsAndUses.qll cannot use this library.
*/
exists(BasicBlock bb, int i |
isSource(source, v) and
bb.getNode(i) = source and
not bb.isUnreachable()
|
exists(int j |
j > i and
sink = bb.getNode(j) and
isSink(sink, v) and
not isBarrier(bb.getNode(pragma[only_bind_into]([i + 1 .. j - 1])), v)
)
or
not exists(int k | isBarrier(bb.getNode(k), v) | k > i) and
bbSuccessorEntryReaches(bb, v, sink, _)
)
}
private predicate reassignment(
ControlFlowNode source, SemanticStackVariable v, ControlFlowNode def, SemanticStackVariable v0
) {
reaches0(source, v, def) and
StackVariableReachability.super.reaches(source, v, def) and
exprDefinition(v0, def, v.getAnAccess())
}
@@ -789,11 +365,11 @@ abstract class StackVariableReachabilityExt extends string {
boolean skipsFirstLoopAlwaysTrueUponEntry
) {
exists(BasicBlock succ, boolean succSkipsFirstLoopAlwaysTrueUponEntry |
bbSuccessorEntryReachesLoopInvariant0(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
bbSuccessorEntryReachesLoopInvariant(bb, succ, skipsFirstLoopAlwaysTrueUponEntry,
succSkipsFirstLoopAlwaysTrueUponEntry) and
not isBarrier(source, bb.getEnd(), succ.getStart(), v)
|
this.bbEntryReachesLocally(source, succ, v, node) and
bbEntryReachesLocally(source, succ, v, node) and
succSkipsFirstLoopAlwaysTrueUponEntry = false
or
not exists(int k | isBarrier(source, succ.getNode(k), succ.getNode(k + 1), v)) and

View File

@@ -19,6 +19,15 @@ class FileWrite extends Expr {
* Gets the expression for the object being written to.
*/
Expr getDest() { fileWrite(this, _, result) }
/**
* Gets the conversion character for this write, if it exists and is known. For example in the following code the write of `value1` has conversion character `"s"`, whereas the write of `value2` has no conversion specifier.
* ```
* fprintf(file, "%s", value1);
* stream << value2;
* ```
*/
string getSourceConvChar(Expr source) { fileWriteWithConvChar(this, source, result) }
}
/**
@@ -150,3 +159,20 @@ private predicate fileWrite(Call write, Expr source, Expr dest) {
// file stream using '<<', 'put' or 'write'
fileStreamChain(write, source, dest)
}
/**
* Whether the function call is a write to a file from 'source' with
* conversion character 'conv'. Does not hold if there isn't a conversion
* character, or if it is unknown (for example the format string is not a
* constant).
*/
private predicate fileWriteWithConvChar(FormattingFunctionCall ffc, Expr source, string conv) {
// fprintf
exists(FormattingFunction f, int n |
f = ffc.getTarget() and
source = ffc.getFormatArgument(n)
|
exists(f.getOutputParameterIndex(true)) and
conv = ffc.(FormattingFunctionCall).getFormat().(FormatLiteral).getConversionChar(n)
)
}

View File

@@ -5,6 +5,8 @@
import cpp
import semmle.code.cpp.controlflow.Dominance
// `GlobalValueNumbering` is only imported to prevent IR re-evaluation.
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils

View File

@@ -14,14 +14,13 @@ private predicate suspicious(string s) {
(
s.matches("%password%") or
s.matches("%passwd%") or
s.matches("%account%") or
s.matches("%accnt%") or
s.matches("%trusted%")
) and
not (
s.matches("%hashed%") or
s.matches("%encrypted%") or
s.matches("%crypt%")
s.matches("%hash%") or
s.matches("%crypt%") or
s.matches("%file%") or
s.matches("%path%")
)
}
@@ -29,14 +28,20 @@ private predicate suspicious(string s) {
* A variable that might contain a password or other sensitive information.
*/
class SensitiveVariable extends Variable {
SensitiveVariable() { suspicious(getName().toLowerCase()) }
SensitiveVariable() {
suspicious(getName().toLowerCase()) and
not this.getUnspecifiedType() instanceof IntegralType
}
}
/**
* A function that might return a password or other sensitive information.
*/
class SensitiveFunction extends Function {
SensitiveFunction() { suspicious(getName().toLowerCase()) }
SensitiveFunction() {
suspicious(getName().toLowerCase()) and
not this.getUnspecifiedType() instanceof IntegralType
}
}
/**

View File

@@ -79,6 +79,7 @@
| file://:0:0:0:0 | declaration of 1st parameter |
| file://:0:0:0:0 | declared_constexpr |
| file://:0:0:0:0 | declared_constinit |
| file://:0:0:0:0 | declared_virtual |
| file://:0:0:0:0 | decltype(nullptr) |
| file://:0:0:0:0 | definition of fp_offset |
| file://:0:0:0:0 | definition of gp_offset |
@@ -91,6 +92,7 @@
| file://:0:0:0:0 | explicit |
| file://:0:0:0:0 | extern |
| file://:0:0:0:0 | far |
| file://:0:0:0:0 | final |
| file://:0:0:0:0 | float |
| file://:0:0:0:0 | forceinline |
| file://:0:0:0:0 | fp_offset |

View File

@@ -49,6 +49,7 @@
| file://:0:0:0:0 | const __va_list_tag & |
| file://:0:0:0:0 | declared_constexpr |
| file://:0:0:0:0 | declared_constinit |
| file://:0:0:0:0 | declared_virtual |
| file://:0:0:0:0 | decltype(nullptr) |
| file://:0:0:0:0 | definition of <error> |
| file://:0:0:0:0 | definition of fp_offset |
@@ -62,6 +63,7 @@
| file://:0:0:0:0 | explicit |
| file://:0:0:0:0 | extern |
| file://:0:0:0:0 | far |
| file://:0:0:0:0 | final |
| file://:0:0:0:0 | float |
| file://:0:0:0:0 | forceinline |
| file://:0:0:0:0 | fp_offset |

View File

@@ -1,21 +1,13 @@
| fields.cpp:3:8:3:12 | Entry | fields.cpp:4:9:4:12 | name | public | CharPointerType | char |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:4:9:4:12 | name | public | PointerDumpType | char |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:5:8:5:8 | t | public | Enum | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:5:8:5:8 | t | public | UserDumpType | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:6:9:6:9 | s | public | CharPointerType | char |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:6:9:6:9 | s | public | PointerDumpType | char |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:7:7:7:7 | i | public | IntType | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:7:7:7:7 | i | public | IntegralDumpType | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:7:7:7:7 | i | public | MicrosoftInt32Type | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:9:7:9:14 | internal | private | IntType | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:9:7:9:14 | internal | private | IntegralDumpType | |
| fields.cpp:3:8:3:12 | Entry | fields.cpp:9:7:9:14 | internal | private | MicrosoftInt32Type | |
| fields.cpp:12:7:12:10 | Name | fields.cpp:13:15:13:15 | s | private | PointerDumpType | const char |
| fields.cpp:16:7:16:11 | Table | fields.cpp:17:9:17:9 | p | private | PointerDumpType | Name |
| fields.cpp:12:7:12:10 | Name | fields.cpp:13:15:13:15 | s | private | PointerType | const char |
| fields.cpp:16:7:16:11 | Table | fields.cpp:17:9:17:9 | p | private | PointerType | Name |
| fields.cpp:16:7:16:11 | Table | fields.cpp:18:7:18:8 | sz | private | IntType | |
| fields.cpp:16:7:16:11 | Table | fields.cpp:18:7:18:8 | sz | private | IntegralDumpType | |
| fields.cpp:16:7:16:11 | Table | fields.cpp:18:7:18:8 | sz | private | MicrosoftInt32Type | |
| fields.cpp:26:7:26:10 | Date | fields.cpp:28:16:28:26 | cache_valid | private | BoolType | |
| fields.cpp:26:7:26:10 | Date | fields.cpp:28:16:28:26 | cache_valid | private | IntegralDumpType | |
| fields.cpp:26:7:26:10 | Date | fields.cpp:30:17:30:21 | cache | public | CharPointerType | char |
| fields.cpp:26:7:26:10 | Date | fields.cpp:30:17:30:21 | cache | public | PointerDumpType | char |

View File

@@ -1 +1 @@
| routinetype.cpp:2:7:2:19 | myRoutineType | file://:0:0:0:0 | ..()(..) | PrintableElement, RoutineDumpType |
| routinetype.cpp:2:7:2:19 | myRoutineType | file://:0:0:0:0 | ..()(..) | RoutineType |

View File

@@ -43,10 +43,12 @@
| Function | specifiers2pp.cpp:10:18:10:24 | MyClass | MyClass | public |
| Function | specifiers2pp.cpp:12:14:12:22 | publicFun | publicFun | inline |
| Function | specifiers2pp.cpp:12:14:12:22 | publicFun | publicFun | public |
| Function | specifiers2pp.cpp:13:21:13:26 | getInt | getInt | declared_virtual |
| Function | specifiers2pp.cpp:13:21:13:26 | getInt | getInt | extern |
| Function | specifiers2pp.cpp:13:21:13:26 | getInt | getInt | public |
| Function | specifiers2pp.cpp:13:21:13:26 | getInt | getInt | pure |
| Function | specifiers2pp.cpp:13:21:13:26 | getInt | getInt | virtual |
| Function | specifiers2pp.cpp:14:21:14:21 | f | f | declared_virtual |
| Function | specifiers2pp.cpp:14:21:14:21 | f | f | extern |
| Function | specifiers2pp.cpp:14:21:14:21 | f | f | public |
| Function | specifiers2pp.cpp:14:21:14:21 | f | f | virtual |
@@ -71,6 +73,7 @@
| Function | specifiers2pp.cpp:24:7:24:7 | operator= | operator= | inline |
| Function | specifiers2pp.cpp:24:7:24:7 | operator= | operator= | public |
| Function | specifiers2pp.cpp:24:7:24:7 | operator= | operator= | public |
| Function | specifiers2pp.cpp:26:21:26:21 | f | f | declared_virtual |
| Function | specifiers2pp.cpp:26:21:26:21 | f | f | extern |
| Function | specifiers2pp.cpp:26:21:26:21 | f | f | override |
| Function | specifiers2pp.cpp:26:21:26:21 | f | f | public |

View File

@@ -109,6 +109,7 @@
| file://:0:0:0:0 | declaration of 1st parameter |
| file://:0:0:0:0 | declared_constexpr |
| file://:0:0:0:0 | declared_constinit |
| file://:0:0:0:0 | declared_virtual |
| file://:0:0:0:0 | decltype(nullptr) |
| file://:0:0:0:0 | definition of fp_offset |
| file://:0:0:0:0 | definition of gp_offset |
@@ -121,6 +122,7 @@
| file://:0:0:0:0 | explicit |
| file://:0:0:0:0 | extern |
| file://:0:0:0:0 | far |
| file://:0:0:0:0 | final |
| file://:0:0:0:0 | float |
| file://:0:0:0:0 | forceinline |
| file://:0:0:0:0 | fp_offset |

View File

@@ -1,2 +1,2 @@
| typedefs.cpp:6:6:6:7 | f1 | typedefs.cpp:8:15:8:18 | TYPE | CTypedefType, LocalTypedefType, PrintableElement, UserDumpType |
| typedefs.cpp:6:6:6:7 | f1 | typedefs.cpp:9:9:9:9 | D | DirectAccessHolder, LocalClass, MetricClass, PrintableElement, StructLikeClass, UserDumpType |
| typedefs.cpp:6:6:6:7 | f1 | typedefs.cpp:8:15:8:18 | TYPE | CTypedefType, LocalTypedefType |
| typedefs.cpp:6:6:6:7 | f1 | typedefs.cpp:9:9:9:9 | D | DirectAccessHolder, LocalClass, MetricClass, StructLikeClass |

View File

@@ -1,3 +1,3 @@
| file://:0:0:0:0 | __wchar_t * | PointerDumpType, PrintableElement | IntegralDumpType, PrintableElement, Wchar_t, WideCharType |
| file://:0:0:0:0 | const __wchar_t | PrintableElement, SpecifiedDumpType | IntegralDumpType, PrintableElement, Wchar_t, WideCharType |
| file://:0:0:0:0 | wchar_t | IntegralDumpType, PrintableElement, Wchar_t, WideCharType | |
| file://:0:0:0:0 | __wchar_t * | PointerType | Wchar_t, WideCharType |
| file://:0:0:0:0 | const __wchar_t | SpecifiedType | Wchar_t, WideCharType |
| file://:0:0:0:0 | wchar_t | Wchar_t, WideCharType | |

View File

@@ -1,8 +1,8 @@
| cstd_types.cpp:47:13:47:15 | if8 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast8_t, PrintableElement, UserDumpType |
| cstd_types.cpp:48:14:48:17 | if16 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast16_t, PrintableElement, UserDumpType |
| cstd_types.cpp:49:14:49:17 | if32 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast32_t, PrintableElement, UserDumpType |
| cstd_types.cpp:50:14:50:17 | if64 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast64_t, PrintableElement, UserDumpType |
| cstd_types.cpp:51:14:51:16 | uf8 | CTypedefType, FastestMinimumWidthIntegralType, PrintableElement, UInt_fast8_t, UserDumpType |
| cstd_types.cpp:52:15:52:18 | uf16 | CTypedefType, FastestMinimumWidthIntegralType, PrintableElement, UInt_fast16_t, UserDumpType |
| cstd_types.cpp:53:15:53:18 | uf32 | CTypedefType, FastestMinimumWidthIntegralType, PrintableElement, UInt_fast32_t, UserDumpType |
| cstd_types.cpp:54:15:54:18 | uf64 | CTypedefType, FastestMinimumWidthIntegralType, PrintableElement, UInt_fast64_t, UserDumpType |
| cstd_types.cpp:47:13:47:15 | if8 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast8_t |
| cstd_types.cpp:48:14:48:17 | if16 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast16_t |
| cstd_types.cpp:49:14:49:17 | if32 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast32_t |
| cstd_types.cpp:50:14:50:17 | if64 | CTypedefType, FastestMinimumWidthIntegralType, Int_fast64_t |
| cstd_types.cpp:51:14:51:16 | uf8 | CTypedefType, FastestMinimumWidthIntegralType, UInt_fast8_t |
| cstd_types.cpp:52:15:52:18 | uf16 | CTypedefType, FastestMinimumWidthIntegralType, UInt_fast16_t |
| cstd_types.cpp:53:15:53:18 | uf32 | CTypedefType, FastestMinimumWidthIntegralType, UInt_fast32_t |
| cstd_types.cpp:54:15:54:18 | uf64 | CTypedefType, FastestMinimumWidthIntegralType, UInt_fast64_t |

View File

@@ -1,8 +1,8 @@
| cstd_types.cpp:31:8:31:9 | i8 | CTypedefType, FixedWidthIntegralType, Int8_t, PrintableElement, UserDumpType |
| cstd_types.cpp:32:9:32:11 | i16 | CTypedefType, FixedWidthIntegralType, Int16_t, PrintableElement, UserDumpType |
| cstd_types.cpp:33:9:33:11 | i32 | CTypedefType, FixedWidthIntegralType, Int32_t, PrintableElement, UserDumpType |
| cstd_types.cpp:34:9:34:11 | i64 | CTypedefType, FixedWidthIntegralType, Int64_t, PrintableElement, UserDumpType |
| cstd_types.cpp:35:9:35:11 | ui8 | CTypedefType, FixedWidthIntegralType, PrintableElement, UInt8_t, UserDumpType |
| cstd_types.cpp:36:10:36:13 | ui16 | CTypedefType, FixedWidthIntegralType, PrintableElement, UInt16_t, UserDumpType |
| cstd_types.cpp:37:10:37:13 | ui32 | CTypedefType, FixedWidthIntegralType, PrintableElement, UInt32_t, UserDumpType |
| cstd_types.cpp:38:10:38:13 | ui64 | CTypedefType, FixedWidthIntegralType, PrintableElement, UInt64_t, UserDumpType |
| cstd_types.cpp:31:8:31:9 | i8 | CTypedefType, FixedWidthIntegralType, Int8_t |
| cstd_types.cpp:32:9:32:11 | i16 | CTypedefType, FixedWidthIntegralType, Int16_t |
| cstd_types.cpp:33:9:33:11 | i32 | CTypedefType, FixedWidthIntegralType, Int32_t |
| cstd_types.cpp:34:9:34:11 | i64 | CTypedefType, FixedWidthIntegralType, Int64_t |
| cstd_types.cpp:35:9:35:11 | ui8 | CTypedefType, FixedWidthIntegralType, UInt8_t |
| cstd_types.cpp:36:10:36:13 | ui16 | CTypedefType, FixedWidthIntegralType, UInt16_t |
| cstd_types.cpp:37:10:37:13 | ui32 | CTypedefType, FixedWidthIntegralType, UInt32_t |
| cstd_types.cpp:38:10:38:13 | ui64 | CTypedefType, FixedWidthIntegralType, UInt64_t |

View File

@@ -1,2 +1,2 @@
| cstd_types.cpp:74:4:74:6 | _e0 | Enum, FixedWidthEnumType, PrintableElement, UserDumpType |
| cstd_types.cpp:75:4:75:6 | _e1 | FixedWidthEnumType, PrintableElement, ScopedEnum, UserDumpType |
| cstd_types.cpp:74:4:74:6 | _e0 | Enum, FixedWidthEnumType |
| cstd_types.cpp:75:4:75:6 | _e1 | FixedWidthEnumType, ScopedEnum |

View File

@@ -1,2 +1,2 @@
| cstd_types.cpp:55:10:55:11 | im | CTypedefType, Intmax_t, MaximumWidthIntegralType, PrintableElement, UserDumpType |
| cstd_types.cpp:56:11:56:13 | uim | CTypedefType, MaximumWidthIntegralType, PrintableElement, Uintmax_t, UserDumpType |
| cstd_types.cpp:55:10:55:11 | im | CTypedefType, Intmax_t, MaximumWidthIntegralType |
| cstd_types.cpp:56:11:56:13 | uim | CTypedefType, MaximumWidthIntegralType, Uintmax_t |

View File

@@ -1,8 +1,8 @@
| cstd_types.cpp:39:15:39:16 | l8 | CTypedefType, Int_least8_t, MinimumWidthIntegralType, PrintableElement, UserDumpType |
| cstd_types.cpp:40:15:40:17 | l16 | CTypedefType, Int_least16_t, MinimumWidthIntegralType, PrintableElement, UserDumpType |
| cstd_types.cpp:41:15:41:17 | l32 | CTypedefType, Int_least32_t, MinimumWidthIntegralType, PrintableElement, UserDumpType |
| cstd_types.cpp:42:15:42:17 | l64 | CTypedefType, Int_least64_t, MinimumWidthIntegralType, PrintableElement, UserDumpType |
| cstd_types.cpp:43:15:43:17 | ul8 | CTypedefType, MinimumWidthIntegralType, PrintableElement, UInt_least8_t, UserDumpType |
| cstd_types.cpp:44:16:44:19 | ul16 | CTypedefType, MinimumWidthIntegralType, PrintableElement, UInt_least16_t, UserDumpType |
| cstd_types.cpp:45:16:45:19 | ul32 | CTypedefType, MinimumWidthIntegralType, PrintableElement, UInt_least32_t, UserDumpType |
| cstd_types.cpp:46:16:46:19 | ul64 | CTypedefType, MinimumWidthIntegralType, PrintableElement, UInt_least64_t, UserDumpType |
| cstd_types.cpp:39:15:39:16 | l8 | CTypedefType, Int_least8_t, MinimumWidthIntegralType |
| cstd_types.cpp:40:15:40:17 | l16 | CTypedefType, Int_least16_t, MinimumWidthIntegralType |
| cstd_types.cpp:41:15:41:17 | l32 | CTypedefType, Int_least32_t, MinimumWidthIntegralType |
| cstd_types.cpp:42:15:42:17 | l64 | CTypedefType, Int_least64_t, MinimumWidthIntegralType |
| cstd_types.cpp:43:15:43:17 | ul8 | CTypedefType, MinimumWidthIntegralType, UInt_least8_t |
| cstd_types.cpp:44:16:44:19 | ul16 | CTypedefType, MinimumWidthIntegralType, UInt_least16_t |
| cstd_types.cpp:45:16:45:19 | ul32 | CTypedefType, MinimumWidthIntegralType, UInt_least32_t |
| cstd_types.cpp:46:16:46:19 | ul64 | CTypedefType, MinimumWidthIntegralType, UInt_least64_t |

View File

@@ -1,4 +1,4 @@
| integral_types.cpp:2:8:2:9 | i8 | file://:0:0:0:0 | char | IntegralDumpType, MicrosoftInt8Type, PlainCharType, PrintableElement |
| integral_types.cpp:3:9:3:11 | i16 | file://:0:0:0:0 | short | IntegralDumpType, MicrosoftInt16Type, PrintableElement, ShortType |
| integral_types.cpp:4:9:4:11 | i32 | file://:0:0:0:0 | int | IntType, IntegralDumpType, MicrosoftInt32Type, PrintableElement |
| integral_types.cpp:5:9:5:11 | i64 | file://:0:0:0:0 | long long | IntegralDumpType, LongLongType, MicrosoftInt64Type, PrintableElement |
| integral_types.cpp:2:8:2:9 | i8 | file://:0:0:0:0 | char | MicrosoftInt8Type, PlainCharType |
| integral_types.cpp:3:9:3:11 | i16 | file://:0:0:0:0 | short | MicrosoftInt16Type, ShortType |
| integral_types.cpp:4:9:4:11 | i32 | file://:0:0:0:0 | int | IntType, MicrosoftInt32Type |
| integral_types.cpp:5:9:5:11 | i64 | file://:0:0:0:0 | long long | LongLongType, MicrosoftInt64Type |

View File

@@ -1,3 +1,3 @@
| file://:0:0:0:0 | wchar_t | IntegralDumpType, PrintableElement, Wchar_t, WideCharType | |
| file://:0:0:0:0 | wchar_t * | PointerDumpType, PrintableElement | CTypedefType, PrintableElement, UserDumpType, Wchar_t |
| ms.c:2:24:2:30 | wchar_t | CTypedefType, PrintableElement, UserDumpType, Wchar_t | |
| file://:0:0:0:0 | wchar_t | Wchar_t, WideCharType | |
| file://:0:0:0:0 | wchar_t * | PointerType | CTypedefType, Wchar_t |
| ms.c:2:24:2:30 | wchar_t | CTypedefType, Wchar_t | |

View File

@@ -41,6 +41,7 @@
| file://:0:0:0:0 | const | Other |
| file://:0:0:0:0 | declared_constexpr | Other |
| file://:0:0:0:0 | declared_constinit | Other |
| file://:0:0:0:0 | declared_virtual | Other |
| file://:0:0:0:0 | decltype(nullptr) | Other |
| file://:0:0:0:0 | definition of fp_offset | Other |
| file://:0:0:0:0 | definition of gp_offset | Other |
@@ -53,6 +54,7 @@
| file://:0:0:0:0 | explicit | Other |
| file://:0:0:0:0 | extern | Other |
| file://:0:0:0:0 | far | Other |
| file://:0:0:0:0 | final | Other |
| file://:0:0:0:0 | float | Other |
| file://:0:0:0:0 | forceinline | Other |
| file://:0:0:0:0 | fp_offset | Other |

View File

@@ -1,94 +1,94 @@
| ..()(..) | PrintableElement, RoutineDumpType | | | | |
| ..(*)(..) | FunctionPointerDumpType, PrintableElement | | ..()(..) | | |
| _Complex __float128 | BinaryFloatingPointType, BuiltInDumpType, ComplexNumberType, PrintableElement | | | | |
| _Complex double | BinaryFloatingPointType, BuiltInDumpType, ComplexNumberType, PrintableElement | | | | |
| _Complex float | BinaryFloatingPointType, BuiltInDumpType, ComplexNumberType, PrintableElement | | | | |
| _Complex long double | BinaryFloatingPointType, BuiltInDumpType, ComplexNumberType, PrintableElement | | | | |
| _Decimal32 | BuiltInDumpType, Decimal32Type, PrintableElement | | | | |
| _Decimal64 | BuiltInDumpType, Decimal64Type, PrintableElement | | | | |
| _Decimal128 | BuiltInDumpType, Decimal128Type, PrintableElement | | | | |
| _Float32 | BinaryFloatingPointType, BuiltInDumpType, PrintableElement, RealNumberType | | | | |
| _Float32x | BinaryFloatingPointType, BuiltInDumpType, PrintableElement, RealNumberType | | | | |
| _Float64 | BinaryFloatingPointType, BuiltInDumpType, PrintableElement, RealNumberType | | | | |
| _Float64x | BinaryFloatingPointType, BuiltInDumpType, PrintableElement, RealNumberType | | | | |
| _Float128 | BinaryFloatingPointType, BuiltInDumpType, PrintableElement, RealNumberType | | | | |
| _Float128x | BinaryFloatingPointType, BuiltInDumpType, PrintableElement, RealNumberType | | | | |
| _Imaginary double | BinaryFloatingPointType, BuiltInDumpType, ImaginaryNumberType, PrintableElement | | | | |
| _Imaginary float | BinaryFloatingPointType, BuiltInDumpType, ImaginaryNumberType, PrintableElement | | | | |
| _Imaginary long double | BinaryFloatingPointType, BuiltInDumpType, ImaginaryNumberType, PrintableElement | | | | |
| __float128 | BuiltInDumpType, Float128Type, PrintableElement | | | | |
| __int128 | Int128Type, IntegralDumpType, PrintableElement | | | | |
| __va_list_tag | DirectAccessHolder, MetricClass, PrintableElement, Struct, StructLikeClass, UserDumpType | | | | |
| __va_list_tag & | LValueReferenceDumpType, PrintableElement | | __va_list_tag | | |
| __va_list_tag && | PrintableElement, RValueReferenceDumpType | | __va_list_tag | | |
| address | DirectAccessHolder, MetricClass, PrintableElement, Struct, StructLikeClass, UserDumpType | | | | |
| address & | LValueReferenceDumpType, PrintableElement | | address | | |
| address && | PrintableElement, RValueReferenceDumpType | | address | | |
| auto | AutoType, PrintableElement, UserDumpType | | | | |
| bool | BoolType, IntegralDumpType, PrintableElement | | | | |
| char | IntegralDumpType, MicrosoftInt8Type, PlainCharType, PrintableElement | | | | |
| char8_t | Char8Type, IntegralDumpType, PrintableElement | | | | |
| char16_t | Char16Type, IntegralDumpType, PrintableElement | | | | |
| char32_t | Char32Type, IntegralDumpType, PrintableElement | | | | |
| char * | CharPointerType, PointerDumpType, PrintableElement | | char | | |
| char *[3] | ArrayDumpType, PrintableElement | char * | char * | | |
| char *[32] | ArrayDumpType, PrintableElement | char * | char * | | |
| char *[] | ArrayDumpType, PrintableElement | char * | char * | | |
| char[2] | ArrayDumpType, PrintableElement | char | char | | |
| char[3] | ArrayDumpType, PrintableElement | char | char | | |
| char[5] | ArrayDumpType, PrintableElement | char | char | | |
| char[6] | ArrayDumpType, PrintableElement | char | char | | |
| char[8] | ArrayDumpType, PrintableElement | char | char | | |
| char[9] | ArrayDumpType, PrintableElement | char | char | | |
| char[10] | ArrayDumpType, PrintableElement | char | char | | |
| char[53] | ArrayDumpType, PrintableElement | char | char | | |
| char[] | ArrayDumpType, PrintableElement | char | char | | |
| const __va_list_tag | PrintableElement, SpecifiedDumpType | | __va_list_tag | | |
| const __va_list_tag & | LValueReferenceDumpType, PrintableElement | | const __va_list_tag | | |
| const address | PrintableElement, SpecifiedDumpType | | address | | |
| const address & | LValueReferenceDumpType, PrintableElement | | const address | | |
| const char | PrintableElement, SpecifiedDumpType | | char | | |
| const char * | PointerDumpType, PrintableElement | | const char | | |
| const char *[3] | ArrayDumpType, PrintableElement | const char * | const char * | | |
| const char *[] | ArrayDumpType, PrintableElement | const char * | const char * | | |
| const char[5] | ArrayDumpType, PrintableElement | const char | const char | | |
| const char[6] | ArrayDumpType, PrintableElement | const char | const char | | |
| const char[8] | ArrayDumpType, PrintableElement | const char | const char | | |
| const char[9] | ArrayDumpType, PrintableElement | const char | const char | | |
| const char[10] | ArrayDumpType, PrintableElement | const char | const char | | |
| const char[53] | ArrayDumpType, PrintableElement | const char | const char | | |
| const double | PrintableElement, SpecifiedDumpType | | double | | |
| const int | PrintableElement, SpecifiedDumpType | | int | | |
| decltype(nullptr) | BuiltInDumpType, NullPointerType, PrintableElement | | | | |
| double | BuiltInDumpType, DoubleType, PrintableElement | | | | |
| error | BuiltInDumpType, ErroneousType, PrintableElement | | | | |
| float | BuiltInDumpType, FloatType, PrintableElement | | | | |
| float[3] | ArrayDumpType, PrintableElement | float | float | | |
| int | IntType, IntegralDumpType, MicrosoftInt32Type, PrintableElement | | | | |
| int * | IntPointerType, PointerDumpType, PrintableElement | | int | | |
| int[4] | ArrayDumpType, PrintableElement | int | int | | |
| int[8] | ArrayDumpType, PrintableElement | int | int | | |
| int[10] | ArrayDumpType, PrintableElement | int | int | | |
| int[10][20] | ArrayDumpType, PrintableElement | int[20] | int[20] | | |
| int[20] | ArrayDumpType, PrintableElement | int | int | | |
| int[] | ArrayDumpType, PrintableElement | int | int | | |
| long | IntegralDumpType, LongType, PrintableElement | | | | |
| long double | BuiltInDumpType, LongDoubleType, PrintableElement | | | | |
| long long | IntegralDumpType, LongLongType, MicrosoftInt64Type, PrintableElement | | | | |
| short | IntegralDumpType, MicrosoftInt16Type, PrintableElement, ShortType | | | | |
| signed __int128 | Int128Type, IntegralDumpType, PrintableElement | | | | |
| signed char | IntegralDumpType, PrintableElement, SignedCharType | | | | |
| signed int | IntType, IntegralDumpType, PrintableElement | | | | |
| signed long | IntegralDumpType, LongType, PrintableElement | | | | |
| signed long long | IntegralDumpType, LongLongType, PrintableElement | | | | |
| signed short | IntegralDumpType, PrintableElement, ShortType | | | | |
| unknown | BuiltInDumpType, PrintableElement, UnknownType | | | | |
| unsigned __int128 | Int128Type, IntegralDumpType, PrintableElement | | | | unsigned integral |
| unsigned char | IntegralDumpType, PrintableElement, UnsignedCharType | | | | unsigned integral |
| unsigned int | IntType, IntegralDumpType, PrintableElement | | | unsigned int | unsigned integral |
| unsigned long | IntegralDumpType, LongType, PrintableElement | | | | unsigned integral |
| unsigned long long | IntegralDumpType, LongLongType, PrintableElement | | | | unsigned integral |
| unsigned short | IntegralDumpType, PrintableElement, ShortType | | | | unsigned integral |
| void | BuiltInDumpType, PrintableElement, VoidType | | | | |
| void * | PointerDumpType, PrintableElement, VoidPointerType | | void | | |
| wchar_t | IntegralDumpType, PrintableElement, Wchar_t, WideCharType | | | | |
| ..()(..) | RoutineType | | | | |
| ..(*)(..) | FunctionPointerType | | ..()(..) | | |
| _Complex __float128 | BinaryFloatingPointType, ComplexNumberType | | | | |
| _Complex double | BinaryFloatingPointType, ComplexNumberType | | | | |
| _Complex float | BinaryFloatingPointType, ComplexNumberType | | | | |
| _Complex long double | BinaryFloatingPointType, ComplexNumberType | | | | |
| _Decimal32 | Decimal32Type | | | | |
| _Decimal64 | Decimal64Type | | | | |
| _Decimal128 | Decimal128Type | | | | |
| _Float32 | BinaryFloatingPointType, RealNumberType | | | | |
| _Float32x | BinaryFloatingPointType, RealNumberType | | | | |
| _Float64 | BinaryFloatingPointType, RealNumberType | | | | |
| _Float64x | BinaryFloatingPointType, RealNumberType | | | | |
| _Float128 | BinaryFloatingPointType, RealNumberType | | | | |
| _Float128x | BinaryFloatingPointType, RealNumberType | | | | |
| _Imaginary double | BinaryFloatingPointType, ImaginaryNumberType | | | | |
| _Imaginary float | BinaryFloatingPointType, ImaginaryNumberType | | | | |
| _Imaginary long double | BinaryFloatingPointType, ImaginaryNumberType | | | | |
| __float128 | Float128Type | | | | |
| __int128 | Int128Type | | | | |
| __va_list_tag | DirectAccessHolder, MetricClass, Struct, StructLikeClass | | | | |
| __va_list_tag & | LValueReferenceType | | __va_list_tag | | |
| __va_list_tag && | RValueReferenceType | | __va_list_tag | | |
| address | DirectAccessHolder, MetricClass, Struct, StructLikeClass | | | | |
| address & | LValueReferenceType | | address | | |
| address && | RValueReferenceType | | address | | |
| auto | AutoType | | | | |
| bool | BoolType | | | | |
| char | MicrosoftInt8Type, PlainCharType | | | | |
| char8_t | Char8Type | | | | |
| char16_t | Char16Type | | | | |
| char32_t | Char32Type | | | | |
| char * | CharPointerType | | char | | |
| char *[3] | ArrayType | char * | char * | | |
| char *[32] | ArrayType | char * | char * | | |
| char *[] | ArrayType | char * | char * | | |
| char[2] | ArrayType | char | char | | |
| char[3] | ArrayType | char | char | | |
| char[5] | ArrayType | char | char | | |
| char[6] | ArrayType | char | char | | |
| char[8] | ArrayType | char | char | | |
| char[9] | ArrayType | char | char | | |
| char[10] | ArrayType | char | char | | |
| char[53] | ArrayType | char | char | | |
| char[] | ArrayType | char | char | | |
| const __va_list_tag | SpecifiedType | | __va_list_tag | | |
| const __va_list_tag & | LValueReferenceType | | const __va_list_tag | | |
| const address | SpecifiedType | | address | | |
| const address & | LValueReferenceType | | const address | | |
| const char | SpecifiedType | | char | | |
| const char * | PointerType | | const char | | |
| const char *[3] | ArrayType | const char * | const char * | | |
| const char *[] | ArrayType | const char * | const char * | | |
| const char[5] | ArrayType | const char | const char | | |
| const char[6] | ArrayType | const char | const char | | |
| const char[8] | ArrayType | const char | const char | | |
| const char[9] | ArrayType | const char | const char | | |
| const char[10] | ArrayType | const char | const char | | |
| const char[53] | ArrayType | const char | const char | | |
| const double | SpecifiedType | | double | | |
| const int | SpecifiedType | | int | | |
| decltype(nullptr) | NullPointerType | | | | |
| double | DoubleType | | | | |
| error | ErroneousType | | | | |
| float | FloatType | | | | |
| float[3] | ArrayType | float | float | | |
| int | IntType, MicrosoftInt32Type | | | | |
| int * | IntPointerType | | int | | |
| int[4] | ArrayType | int | int | | |
| int[8] | ArrayType | int | int | | |
| int[10] | ArrayType | int | int | | |
| int[10][20] | ArrayType | int[20] | int[20] | | |
| int[20] | ArrayType | int | int | | |
| int[] | ArrayType | int | int | | |
| long | LongType | | | | |
| long double | LongDoubleType | | | | |
| long long | LongLongType, MicrosoftInt64Type | | | | |
| short | MicrosoftInt16Type, ShortType | | | | |
| signed __int128 | Int128Type | | | | |
| signed char | SignedCharType | | | | |
| signed int | IntType | | | | |
| signed long | LongType | | | | |
| signed long long | LongLongType | | | | |
| signed short | ShortType | | | | |
| unknown | UnknownType | | | | |
| unsigned __int128 | Int128Type | | | | unsigned integral |
| unsigned char | UnsignedCharType | | | | unsigned integral |
| unsigned int | IntType | | | unsigned int | unsigned integral |
| unsigned long | LongType | | | | unsigned integral |
| unsigned long long | LongLongType | | | | unsigned integral |
| unsigned short | ShortType | | | | unsigned integral |
| void | VoidType | | | | |
| void * | VoidPointerType | | void | | |
| wchar_t | Wchar_t, WideCharType | | | | |

View File

@@ -1,111 +1,70 @@
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | __va_list_tag && | DumpVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | __va_list_tag && | SemanticStackVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | address && | DumpVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | address && | SemanticStackVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | const __va_list_tag & | DumpVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | const __va_list_tag & | SemanticStackVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | const address & | DumpVariable | | |
| file://:0:0:0:0 | (unnamed parameter 0) | file://:0:0:0:0 | const address & | SemanticStackVariable | | |
| file://:0:0:0:0 | fp_offset | file://:0:0:0:0 | unsigned int | DumpVariable | | |
| file://:0:0:0:0 | fp_offset | file://:0:0:0:0 | unsigned int | Field | | |
| file://:0:0:0:0 | gp_offset | file://:0:0:0:0 | unsigned int | DumpVariable | | |
| file://:0:0:0:0 | gp_offset | file://:0:0:0:0 | unsigned int | Field | | |
| file://:0:0:0:0 | overflow_arg_area | file://:0:0:0:0 | void * | DumpVariable | | |
| file://:0:0:0:0 | overflow_arg_area | file://:0:0:0:0 | void * | Field | | |
| file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | DumpVariable | | |
| file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | Field | | |
| variables.cpp:1:12:1:12 | i | file://:0:0:0:0 | int | DumpVariable | | |
| variables.cpp:1:12:1:12 | i | file://:0:0:0:0 | int | GlobalVariable | | |
| variables.cpp:1:12:1:12 | i | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
| variables.cpp:2:12:2:12 | i | file://:0:0:0:0 | int | DumpVariable | | |
| variables.cpp:2:12:2:12 | i | file://:0:0:0:0 | int | GlobalVariable | | |
| variables.cpp:2:12:2:12 | i | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
| variables.cpp:3:12:3:12 | i | file://:0:0:0:0 | int | DumpVariable | | |
| variables.cpp:3:12:3:12 | i | file://:0:0:0:0 | int | GlobalVariable | | |
| variables.cpp:3:12:3:12 | i | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
| variables.cpp:5:11:5:11 | c | file://:0:0:0:0 | const int | DumpVariable | const | static |
| variables.cpp:5:11:5:11 | c | file://:0:0:0:0 | const int | GlobalVariable | const | static |
| variables.cpp:5:11:5:11 | c | file://:0:0:0:0 | const int | StaticStorageDurationVariable | const | static |
| variables.cpp:6:14:6:15 | pi | file://:0:0:0:0 | const double | DumpVariable | const | static |
| variables.cpp:6:14:6:15 | pi | file://:0:0:0:0 | const double | GlobalVariable | const | static |
| variables.cpp:6:14:6:15 | pi | file://:0:0:0:0 | const double | StaticStorageDurationVariable | const | static |
| variables.cpp:8:10:8:10 | a | file://:0:0:0:0 | unsigned int | DumpVariable | | |
| variables.cpp:8:10:8:10 | a | file://:0:0:0:0 | unsigned int | GlobalVariable | | |
| variables.cpp:8:10:8:10 | a | file://:0:0:0:0 | unsigned int | StaticStorageDurationVariable | | |
| variables.cpp:10:14:10:14 | b | file://:0:0:0:0 | unsigned int | DumpVariable | | |
| variables.cpp:10:14:10:14 | b | file://:0:0:0:0 | unsigned int | GlobalVariable | | |
| variables.cpp:10:14:10:14 | b | file://:0:0:0:0 | unsigned int | StaticStorageDurationVariable | | |
| variables.cpp:12:13:12:17 | kings | file://:0:0:0:0 | const char *[] | DumpVariable | | |
| variables.cpp:12:13:12:17 | kings | file://:0:0:0:0 | const char *[] | GlobalVariable | | |
| variables.cpp:12:13:12:17 | kings | file://:0:0:0:0 | const char *[] | StaticStorageDurationVariable | | |
| variables.cpp:14:6:14:6 | p | file://:0:0:0:0 | int * | DumpVariable | | |
| variables.cpp:14:6:14:6 | p | file://:0:0:0:0 | int * | GlobalVariable | | |
| variables.cpp:14:6:14:6 | p | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
| variables.cpp:14:9:14:9 | q | file://:0:0:0:0 | int | DumpVariable | | |
| variables.cpp:14:9:14:9 | q | file://:0:0:0:0 | int | GlobalVariable | | |
| variables.cpp:14:9:14:9 | q | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |
| variables.cpp:15:12:15:13 | v1 | file://:0:0:0:0 | int[10] | DumpVariable | | static |
| variables.cpp:15:12:15:13 | v1 | file://:0:0:0:0 | int[10] | GlobalVariable | | static |
| variables.cpp:15:12:15:13 | v1 | file://:0:0:0:0 | int[10] | StaticStorageDurationVariable | | static |
| variables.cpp:15:21:15:22 | pv | file://:0:0:0:0 | int * | DumpVariable | | static |
| variables.cpp:15:21:15:22 | pv | file://:0:0:0:0 | int * | GlobalVariable | | static |
| variables.cpp:15:21:15:22 | pv | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | static |
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | DumpVariable | | |
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | FunctionPointerVariable | | |
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | GlobalVariable | | |
| variables.cpp:17:7:17:8 | fp | file://:0:0:0:0 | ..(*)(..) | StaticStorageDurationVariable | | |
| variables.cpp:19:7:19:8 | v2 | file://:0:0:0:0 | float[3] | DumpVariable | | |
| variables.cpp:19:7:19:8 | v2 | file://:0:0:0:0 | float[3] | GlobalVariable | | |
| variables.cpp:19:7:19:8 | v2 | file://:0:0:0:0 | float[3] | StaticStorageDurationVariable | | |
| variables.cpp:20:7:20:8 | v3 | file://:0:0:0:0 | char *[32] | DumpVariable | | |
| variables.cpp:20:7:20:8 | v3 | file://:0:0:0:0 | char *[32] | GlobalVariable | | |
| variables.cpp:20:7:20:8 | v3 | file://:0:0:0:0 | char *[32] | StaticStorageDurationVariable | | |
| variables.cpp:22:5:22:6 | d2 | file://:0:0:0:0 | int[10][20] | DumpVariable | | |
| variables.cpp:22:5:22:6 | d2 | file://:0:0:0:0 | int[10][20] | GlobalVariable | | |
| variables.cpp:22:5:22:6 | d2 | file://:0:0:0:0 | int[10][20] | StaticStorageDurationVariable | | |
| variables.cpp:24:6:24:7 | v4 | file://:0:0:0:0 | char[3] | DumpVariable | | |
| variables.cpp:24:6:24:7 | v4 | file://:0:0:0:0 | char[3] | GlobalVariable | | |
| variables.cpp:24:6:24:7 | v4 | file://:0:0:0:0 | char[3] | StaticStorageDurationVariable | | |
| variables.cpp:26:5:26:6 | v5 | file://:0:0:0:0 | int[8] | DumpVariable | | |
| variables.cpp:26:5:26:6 | v5 | file://:0:0:0:0 | int[8] | GlobalVariable | | |
| variables.cpp:26:5:26:6 | v5 | file://:0:0:0:0 | int[8] | StaticStorageDurationVariable | | |
| variables.cpp:28:7:28:8 | p2 | file://:0:0:0:0 | char * | DumpVariable | | |
| variables.cpp:28:7:28:8 | p2 | file://:0:0:0:0 | char * | GlobalVariable | | |
| variables.cpp:28:7:28:8 | p2 | file://:0:0:0:0 | char * | StaticStorageDurationVariable | | |
| variables.cpp:29:6:29:7 | p3 | file://:0:0:0:0 | char[] | DumpVariable | | |
| variables.cpp:29:6:29:7 | p3 | file://:0:0:0:0 | char[] | GlobalVariable | | |
| variables.cpp:29:6:29:7 | p3 | file://:0:0:0:0 | char[] | StaticStorageDurationVariable | | |
| variables.cpp:31:6:31:10 | alpha | file://:0:0:0:0 | char[] | DumpVariable | | |
| variables.cpp:31:6:31:10 | alpha | file://:0:0:0:0 | char[] | GlobalVariable | | |
| variables.cpp:31:6:31:10 | alpha | file://:0:0:0:0 | char[] | StaticStorageDurationVariable | | |
| variables.cpp:34:5:34:6 | av | file://:0:0:0:0 | int[] | DumpVariable | | |
| variables.cpp:34:5:34:6 | av | file://:0:0:0:0 | int[] | GlobalVariable | | |
| variables.cpp:34:5:34:6 | av | file://:0:0:0:0 | int[] | StaticStorageDurationVariable | | |
| variables.cpp:35:6:35:8 | ap1 | file://:0:0:0:0 | int * | DumpVariable | | |
| variables.cpp:35:6:35:8 | ap1 | file://:0:0:0:0 | int * | GlobalVariable | | |
| variables.cpp:35:6:35:8 | ap1 | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
| variables.cpp:36:6:36:8 | ap2 | file://:0:0:0:0 | int * | DumpVariable | | |
| variables.cpp:36:6:36:8 | ap2 | file://:0:0:0:0 | int * | GlobalVariable | | |
| variables.cpp:36:6:36:8 | ap2 | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
| variables.cpp:37:6:37:8 | ap3 | file://:0:0:0:0 | int * | DumpVariable | | |
| variables.cpp:37:6:37:8 | ap3 | file://:0:0:0:0 | int * | GlobalVariable | | |
| variables.cpp:37:6:37:8 | ap3 | file://:0:0:0:0 | int * | StaticStorageDurationVariable | | |
| variables.cpp:41:7:41:11 | local | file://:0:0:0:0 | char[] | DumpVariable | | |
| variables.cpp:41:7:41:11 | local | file://:0:0:0:0 | char[] | LocalVariable | | |
| variables.cpp:41:7:41:11 | local | file://:0:0:0:0 | char[] | SemanticStackVariable | | |
| variables.cpp:43:14:43:18 | local | file://:0:0:0:0 | int | DumpVariable | | static |
| variables.cpp:43:14:43:18 | local | file://:0:0:0:0 | int | StaticLocalVariable | | static |
| variables.cpp:48:9:48:12 | name | file://:0:0:0:0 | char * | DumpVariable | | |
| variables.cpp:48:9:48:12 | name | file://:0:0:0:0 | char * | Field | | |
| variables.cpp:49:12:49:17 | number | file://:0:0:0:0 | long | DumpVariable | | |
| variables.cpp:49:12:49:17 | number | file://:0:0:0:0 | long | Field | | |
| variables.cpp:50:9:50:14 | street | file://:0:0:0:0 | char * | DumpVariable | | |
| variables.cpp:50:9:50:14 | street | file://:0:0:0:0 | char * | Field | | |
| variables.cpp:51:9:51:12 | town | file://:0:0:0:0 | char * | DumpVariable | | |
| variables.cpp:51:9:51:12 | town | file://:0:0:0:0 | char * | Field | | |
| variables.cpp:52:16:52:22 | country | file://:0:0:0:0 | char * | DumpVariable | | static |
| variables.cpp:52:16:52:22 | country | file://:0:0:0:0 | char * | MemberVariable | | static |
| variables.cpp:52:16:52:22 | country | file://:0:0:0:0 | char * | StaticStorageDurationVariable | | static |
| variables.cpp:56:14:56:29 | externInFunction | file://:0:0:0:0 | int | DumpVariable | | |
| variables.cpp:56:14:56:29 | externInFunction | file://:0:0:0:0 | int | GlobalVariable | | |
| variables.cpp:56:14:56:29 | externInFunction | file://:0:0:0:0 | int | StaticStorageDurationVariable | | |

View File

@@ -1,3 +1,10 @@
| test2.cpp:43:2:43:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:43:36:43:43 | password | this source. |
| test2.cpp:44:2:44:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:44:37:44:45 | thepasswd | this source. |
| test2.cpp:50:2:50:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:50:41:50:53 | passwd_config | this source. |
| test2.cpp:54:2:54:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:54:41:54:52 | widepassword | this source. |
| test2.cpp:55:2:55:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:55:40:55:51 | widepassword | this source. |
| test2.cpp:57:2:57:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:57:39:57:49 | call to getPassword | this source. |
| test2.cpp:65:3:65:9 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:62:18:62:25 | password | this source. |
| test.cpp:45:3:45:7 | call to fputs | This write into file 'file' may contain unencrypted data from $@ | test.cpp:45:9:45:19 | thePassword | this source. |
| test.cpp:70:35:70:35 | call to operator<< | This write into file 'mystream' may contain unencrypted data from $@ | test.cpp:70:38:70:48 | thePassword | this source. |
| test.cpp:73:37:73:41 | call to write | This write into file 'mystream' may contain unencrypted data from $@ | test.cpp:73:43:73:53 | thePassword | this source. |

View File

@@ -0,0 +1,94 @@
#define FILE int
typedef unsigned long size_t;
FILE *fopen(const char *filename, const char *mode);
int snprintf(char *s, size_t n, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
char *strcpy(char *s1, const char *s2);
char *crypt(char *input);
struct myStruct
{
// sensitive
char *password;
char *thepasswd;
char *accountkey;
wchar_t *widepassword;
// encrypted
char password_hash[64];
char *encrypted_passwd;
// not sensitive
char *password_file;
char *password_path;
int num_passwords;
int *password_tries;
bool have_passwd;
// dubious
char *passwd_config;
char *passwd_config2;
};
char *getPassword();
char *getPasswordHash();
int getPasswordMaxChars();
void tests(FILE *log, myStruct &s)
{
fprintf(log, "password = %s\n", s.password); // BAD
fprintf(log, "thepasswd = %s\n", s.thepasswd); // BAD
fprintf(log, "accountkey = %s\n", s.accountkey); // DUBIOUS [NOT REPORTED]
fprintf(log, "password_hash = %s\n", s.password_hash); // GOOD
fprintf(log, "encrypted_passwd = %s\n", s.encrypted_passwd); // GOOD
fprintf(log, "password_file = %s\n", s.password_file); // GOOD
fprintf(log, "password_path = %s\n", s.password_path); // GOOD
fprintf(log, "passwd_config = %s\n", s.passwd_config); // DUBIOUS [REPORTED]
fprintf(log, "num_passwords = %i\n", s.num_passwords); // GOOD
fprintf(log, "password_tries = %i\n", *(s.password_tries)); // GOOD
fprintf(log, "have_passwd = %i\n", s.have_passwd); // GOOD
fprintf(log, "widepassword = %ls\n", s.widepassword); // BAD
fprintf(log, "widepassword = %S\n", s.widepassword); // BAD
fprintf(log, "getPassword() = %s\n", getPassword()); // BAD
fprintf(log, "getPasswordHash() = %s\n", getPasswordHash()); // GOOD
fprintf(log, "getPasswordMaxChars() = %i\n", getPasswordMaxChars()); // GOOD
{
char *cpy1 = s.password;
char *cpy2 = crypt(s.password);
fprintf(log, "cpy1 = %s\n", cpy1); // BAD
fprintf(log, "cpy2 = %s\n", cpy2); // GOOD
}
{
char buf[1024];
strcpy(buf, s.password);
fprintf(log, "buf = %s\n", buf); // BAD [NOT DETECTED]
strcpy(buf, s.password_hash);
fprintf(log, "buf = %s\n", buf); // GOOD
}
fprintf(log, "password = %p\n", s.password); // GOOD
{
if (fopen(s.passwd_config2, "rt") == 0)
{
fprintf(log, "could not open file '%s'.\n", s.passwd_config2); // GOOD
}
}
{
char buffer[1024];
snprintf(buffer, 1024, "password = %s", s.password);
fprintf(log, "log: %s", buffer); // BAD [NOT DETECTED]
}
}

View File

@@ -1,14 +1,13 @@
| test2.cpp:39:7:39:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:39:13:39:16 | path | filename | test2.cpp:34:6:34:10 | call to fopen | checked |
| test2.cpp:52:7:52:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:52:13:52:16 | path | filename | test2.cpp:52:7:52:11 | call to fopen | checked |
| test2.cpp:69:7:69:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:69:13:69:16 | path | filename | test2.cpp:67:6:67:9 | call to stat | checked |
| test2.cpp:98:7:98:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:98:13:98:16 | path | filename | test2.cpp:96:15:96:17 | foo | checked |
| test2.cpp:83:7:83:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:83:13:83:16 | path | filename | test2.cpp:81:6:81:8 | buf | checked |
| test2.cpp:98:7:98:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:98:13:98:16 | path | filename | test2.cpp:96:6:96:12 | buf_ptr | checked |
| test2.cpp:115:7:115:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:115:13:115:16 | path | filename | test2.cpp:113:22:113:24 | buf | checked |
| test2.cpp:130:7:130:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:130:13:130:16 | path | filename | test2.cpp:128:21:128:27 | buf_ptr | checked |
| test2.cpp:157:7:157:10 | call to open | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:157:12:157:15 | path | filename | test2.cpp:155:6:155:9 | call to stat | checked |
| test2.cpp:170:7:170:10 | call to open | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:170:12:170:15 | path | filename | test2.cpp:168:6:168:10 | call to lstat | checked |
| test2.cpp:245:3:245:7 | call to chmod | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:245:9:245:12 | path | filename | test2.cpp:238:6:238:10 | call to fopen | checked |
| test2.cpp:255:3:255:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:255:10:255:14 | path1 | filename | test2.cpp:253:6:253:11 | call to rename | checked |
| test2.cpp:277:7:277:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:277:13:277:16 | path | filename | test2.cpp:275:6:275:11 | call to access | checked |
| test2.cpp:303:7:303:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:303:13:303:16 | path | filename | test2.cpp:301:7:301:12 | call to access | checked |
| test2.cpp:317:7:317:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:317:13:317:16 | path | filename | test2.cpp:313:6:313:11 | call to access | checked |
| test.cpp:21:3:21:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test.cpp:21:10:21:14 | file1 | filename | test.cpp:19:7:19:12 | call to rename | checked |
| test.cpp:35:3:35:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test.cpp:35:10:35:14 | file1 | filename | test.cpp:32:7:32:12 | call to rename | checked |
| test.cpp:49:3:49:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test.cpp:49:10:49:14 | file1 | filename | test.cpp:47:7:47:12 | call to rename | checked |
| test2.cpp:348:3:348:7 | call to chmod | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:348:9:348:12 | path | filename | test2.cpp:341:6:341:10 | call to fopen | checked |
| test2.cpp:356:3:356:7 | call to chmod | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:356:9:356:13 | path2 | filename | test2.cpp:354:7:354:12 | call to rename | checked |

View File

@@ -18,7 +18,7 @@ void test1()
create(file1);
if (!rename(file1, file2))
{
remove(file1); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
remove(file1); // DUBIOUS (bad but perhaps not exploitable)
}
}
@@ -32,7 +32,7 @@ void test2()
if (!rename(file1, file2))
{
file1.set("d.txt");
remove(file1); // GOOD [FALSE POSITIVE]
remove(file1); // GOOD
}
}
@@ -46,6 +46,6 @@ void test3()
create(file1);
if (!rename(file1, file2))
{
remove(file1); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
remove(file1); // DUBIOUS (bad but perhaps not exploitable)
}
}

View File

@@ -36,7 +36,7 @@ void test1_1(const char *path)
if (f == NULL)
{
// retry
f = fopen(path, "r"); // GOOD (this is just trying again) [FALSE POSITIVE]
f = fopen(path, "r"); // GOOD (this is just trying again)
}
// ...
@@ -49,7 +49,7 @@ void test1_2(const char *path)
// try until we succeed
while (f == NULL)
{
f = fopen(path, "r"); // GOOD (this is just trying again) [FALSE POSITIVE]
f = fopen(path, "r"); // GOOD (this is just trying again)
// ...
}
@@ -80,7 +80,7 @@ void test2_2(const char *path)
stat(path, &buf);
if (buf.foo > 0)
{
f = fopen(path, "r"); // BAD [NOT DETECTED]
f = fopen(path, "r"); // BAD
}
// ...
@@ -112,7 +112,7 @@ void test2_4(const char *path)
stat(path, &buf);
if (stat_condition(&buf))
{
f = fopen(path, "r"); // BAD [NOT DETECTED]
f = fopen(path, "r"); // BAD
}
// ...
@@ -127,7 +127,7 @@ void test2_5(const char *path)
stat(path, buf_ptr);
if (stat_condition(buf_ptr))
{
f = fopen(path, "r"); // BAD [NOT DETECTED]
f = fopen(path, "r"); // BAD
}
// ...
@@ -242,7 +242,7 @@ void test4_1(const char *path)
fclose(f);
chmod(path, 0); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
chmod(path, 0); // BAD
}
}
@@ -252,7 +252,7 @@ void test5_1(const char *path1, const char *path2)
{
if (rename(path1, path2))
{
remove(path1); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
remove(path1); // DUBIOUS (bad but perhaps not exploitable)
}
}
@@ -331,3 +331,28 @@ void test6_5(const char *path1, const char *path2)
// ...
}
}
// --- open / rename -> chmod ---
void test7_1(const char *path)
{
FILE *f;
f = fopen(path, "wt");
if (f != 0)
{
// ...
fclose(f);
chmod(path, 1234); // BAD
}
}
void test7_1(const char *path1, const char *path2)
{
if (!rename(path1, path2))
{
chmod(path2, 1234); // BAD
}
}

View File

@@ -7,4 +7,3 @@
| test.cpp:170:6:170:9 | data | Memory pointed to by 'data' may have been previously freed $@ | test.cpp:165:2:165:5 | call to free | here |
| test.cpp:193:6:193:9 | data | Memory pointed to by 'data' may have been previously freed $@ | test.cpp:191:3:191:6 | call to free | here |
| test.cpp:201:6:201:6 | x | Memory pointed to by 'x' may have been previously freed $@ | test.cpp:200:2:200:9 | delete | here |
| test.cpp:242:14:242:17 | data | Memory pointed to by 'data' may have been previously freed $@ | test.cpp:243:11:243:14 | call to free | here |

View File

@@ -213,42 +213,3 @@ void regression_test_for_static_var_handling()
data = (char *)malloc(100*sizeof(char));
use(data); // GOOD
}
void test16(int n, bool b) {
char* data = NULL;
for(int i = 0; i < n; ++i) {
if(b) data = (char*)malloc(10 * sizeof(char));
if(!b || data == NULL) return;
use(data); // GOOD
free(data); // GOOD
}
}
void test17(int n, bool b) {
char* data = (char*)malloc(10);
if(b) {
free(data);
}
if(!b) {
use(data); // GOOD
}
}
void test18(int* array) {
char* data = (char*)malloc(10 * sizeof(char));
for (int i = 0; i < 4; ++i) {
int b = array[i];
if(b) use(data); // BAD
if(!b) free(data);
}
}
void test19(int* array) {
char* data = (char*)malloc(10 * sizeof(char));
int b = array[0];
for (int i = 0; i < 4; ++i) {
if(b) use(data); // GOOD
if(!b) free(data);
}
}

View File

@@ -8,6 +8,7 @@
| test.cpp:132:9:132:9 | j | The variable $@ may not be initialized here. | test.cpp:126:6:126:6 | j | j |
| test.cpp:219:3:219:3 | x | The variable $@ may not be initialized here. | test.cpp:218:7:218:7 | x | x |
| test.cpp:243:13:243:13 | i | The variable $@ may not be initialized here. | test.cpp:241:6:241:6 | i | i |
| test.cpp:329:9:329:11 | val | The variable $@ may not be initialized here. | test.cpp:321:6:321:8 | val | val |
| test.cpp:336:10:336:10 | a | The variable $@ may not be initialized here. | test.cpp:333:7:333:7 | a | a |
| test.cpp:369:10:369:10 | a | The variable $@ may not be initialized here. | test.cpp:358:7:358:7 | a | a |
| test.cpp:378:9:378:11 | val | The variable $@ may not be initialized here. | test.cpp:359:6:359:8 | val | val |

View File

@@ -326,7 +326,7 @@ int test28() {
a = false;
c = false;
}
return val; // GOOD
return val; // GOOD [FALSE POSITIVE]
}
int test29() {

View File

@@ -0,0 +1,104 @@
.. _about-codeql-packs:
About CodeQL packs
==================
.. include:: ../reusables/beta-note-package-management.rst
CodeQL packs are used to create, share, depend on, and run CodeQL queries and libraries. You can publish your own CodeQL packs and download packs created by others. CodeQL packs contain queries, library files, query suites, and metadata.
There are two types of CodeQL packs: query packs and library packs.
* Query packs are designed to be run. When a query pack is published, the bundle includes all the transitive dependencies and a compilation cache. This ensures consistent and efficient execution of the queries in the pack.
* Library packs are designed to be used by query packs (or other library packs) and do not contain queries themselves. The libraries are not compiled and there is no compilation cache included when the pack is published.
You can use the package management commands in the CodeQL CLI to create CodeQL packs, add dependencies to packs, and install or update dependencies. For more information, see ":ref:`Creating and working with CodeQL packs <creating-and-working-with-codeql-packs>`." You can also publish and download CodeQL packs using the CodeQL CLI. For more information, see ":doc:`Publishing and using CodeQL packs <publishing-and-using-codeql-packs>`."
CodeQL pack structure
---------------------
A CodeQL pack must contain a file called ``qlpack.yml`` in its root directory. In the ``qlpack.yml`` file, the ``name:`` field must have a value that follows the format of ``<scope>/<pack>``, where ``<scope>`` is the GitHub organization or user account that the pack will be published to and ``<pack>`` is the name of the pack. The other
files and directories within the pack should be logically organized. For example, typically:
- Queries are organized into directories for specific categories.
- Queries for specific products, libraries, and frameworks are organized into
their own top-level directories.
About ``qlpack.yml`` files
--------------------------
When executing query-related commands, CodeQL first looks in siblings of the installation directory (and their subdirectories) for ``qlpack.yml`` files.
Then it checks the package cache for CodeQL packs which have been downloaded. This means that when you are developing queries locally, the local packages
in the installation directory override packages of the same name in the package cache, so that you can test your local changes.
The metadata in each `qlpack.yml`` file tells
CodeQL how to compile any queries in the pack, what libraries the pack depends on, and where to
find query suite definitions.
The contents of the CodeQL pack (queries or libraries used in CodeQL analysis) is
included in the same directory as ``qlpack.yml``, or its subdirectories.
The location of ``qlpack.yml`` defines the library path for the content
of the CodeQL pack. That is, for all ``.ql`` and ``.qll`` files in the pack,
CodeQL will resolve all import statements relative to the ``qlpack.yml`` at the
pack's root.
.. _codeqlpack-yml-properties:
``qlpack.yml`` properties
~~~~~~~~~~~~~~~~~~~~~~~~~
The following properties are supported in ``qlpack.yml`` files.
.. list-table::
:header-rows: 1
:widths: auto
* - Property
- Example
- Required
- Purpose
* - ``name``
- ``octo-org/security-queries``
- All packs
- The scope, where the CodeQL pack is published, and the name of the pack defined using alphanumeric characters and hyphens. It must be unique as CodeQL cannot differentiate between CodeQL packs with identical names. Name components cannot start or end with a hyphen. Additionally, a period is not allowed in pack names at all. Use the pack name to specify queries to run using ``database analyze`` and to define dependencies between QL packs (see examples below).
* - ``version``
- ``0.0.0``
- All packs
- A version range for this CodeQL pack. This must be a valid semantic version that meets the `SemVer v2.0.0 specification <https://semver.org/spec/v2.0.0.html>`__.
* - ``dependencies``
- ``codeql/javascript-all: ^1.2.3``
- Optional
- The names and version ranges of any CodeQL packs that this pack depends on, as a mapping. This gives the pack access to any libraries, database schema, and query suites defined in the dependency. For more information, see `SemVer ranges <https://docs.npmjs.com/cli/v6/using-npm/semver#ranges>`__ in the NPM documentation.
* - ``suites``
- ``octo-org-query-suites``
- Optional
- The path to a directory in the pack that contains the query suites you want to make known to the CLI, defined relative to the pack directory. QL pack users can run "well-known" suites stored in this directory by specifying the pack name, without providing their full path. This is not supported for CodeQL packs downloaded from a package registry. For more information about query suites, see ":doc:`Creating CodeQL query suites <creating-codeql-query-suites>`."
* - ``extractor``
- ``javascript``
- All test packs
- The CodeQL language extractor to use when the CLI creates a database in the pack. For more information about testing queries, see ":doc:`Testing custom queries <testing-custom-queries>`."
* - ``tests``
- ``.``
- Optional for test packs
- The path to a directory within the pack that contains tests, defined relative to the pack directory. Use ``.`` to specify the whole pack. Any queries in this directory are run as tests when ``test run`` is run with the ``--strict-test-discovery`` option. These queries are ignored by query suite definitions that use ``queries`` or ``qlpack`` instructions to ask for all queries in a particular pack.
* - ``dbscheme``
- ``semmlecode.python.dbscheme``
- Core language packs only
- The path to the :ref:`database schema <codeql-database-schema>` for all libraries and queries written for this CodeQL language (see example below).
* - ``upgrades``
- ``.``
- Core language packs only
- The path to a directory within the pack that contains upgrade scripts, defined relative to the pack directory. The ``database upgrade`` action uses these scripts to update databases that were created by an older version of an extractor so they're compatible with the current extractor (see `Upgrade scripts for a language <#upgrade-scripts-for-a-language>`__ below.)
* - ``authors``
- ``example@github.com``
- All packs
- Metadata that will be displayed on the packaging search page in the packages section of the account that the CodeQL pack is published to.
* - ``licenses``
- ``(LGPL-2.1 AND MIT)``
- All packs
- Metadata that will be displayed on the packaging search page in the packages section of the account that the CodeQL pack is published to. For a list of allowed licenses, see `SPDX License List <https://spdx.org/licenses/>`__ in the SPDX Specification.
* - ``description``
- ``Human-readable description of the contents of the CodeQL pack.``
- All packs
- Metadata that will be displayed on the packaging search page in the packages section of the account that the CodeQL pack is published to.

View File

@@ -87,7 +87,7 @@ The following properties are supported in ``qlpack.yml`` files.
* - ``suites``
- ``suites``
- Optional
- The path to a directory that contains the "well-known" query suites in the pack, defined relative to the pack directory. You can run "well-known" suites stored in this directory by specifying the pack name, without providing their full path. To use query suites stored in other directories in the pack, you must provide their full path. For more information about query suites, see ":doc:`Creating CodeQL query suites <creating-codeql-query-suites>`."
- The path to a directory in the pack that contains the query suites you want to make known to the CLI, defined relative to the pack directory. QL pack users can run "well-known" suites stored in this directory by specifying the pack name, without providing their full path. For more information about query suites, see ":doc:`Creating CodeQL query suites <creating-codeql-query-suites>`."
* - ``extractor``
- ``javascript``
- All test packs

View File

@@ -16,9 +16,9 @@ For information about writing queries to run with ``database analyze``, see
Before starting an analysis you must:
- :doc:`Set up the CodeQL CLI <getting-started-with-the-codeql-cli>` so that it can find the queries
and libraries included in the CodeQL repository.
- :doc:`Create a CodeQL database <creating-codeql-databases>` for the source
code you want to analyze.
and libraries included in the CodeQL repository.
- :doc:`Create a CodeQL database <creating-codeql-databases>` for the source
code you want to analyze.
Running ``codeql database analyze``
@@ -65,7 +65,7 @@ You can also specify:
- .. include:: ../reusables/threads-query-execution.rst
.. pull-quote::
.. pull-quote::
Upgrading databases
@@ -94,7 +94,7 @@ Running a single query
To run a single query over a CodeQL database for a JavaScript codebase,
you could use the following command from the directory containing your database::
codeql database analyze <javascript-database> ../ql/javascript/ql/src/Declarations/UnusedVariable.ql --format=csv --output=js-analysis/js-results.csv
codeql database analyze <javascript-database> ../ql/javascript/ql/src/Declarations/UnusedVariable.ql --format=csv --output=js-analysis/js-results.csv
This command runs a simple query that finds potential bugs related to unused
variables, imports, functions, or classes---it is one of the JavaScript
@@ -102,28 +102,44 @@ queries included in the CodeQL repository. You could run more than one query by
specifying a space-separated list of similar paths.
The analysis generates a CSV file (``js-results.csv``) in a new directory
(``js-analysis``).
(``js-analysis``).
You can also run your own custom queries with the ``database analyze`` command.
For more information about preparing your queries to use with the CodeQL CLI,
see ":doc:`Using custom queries with the CodeQL CLI <using-custom-queries-with-the-codeql-cli>`."
Running GitHub code scanning suites
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running a CodeQL pack
~~~~~~~~~~~~~~~~~~~~~
To run the GitHub code scanning suite of queries over a CodeQL database for a C/C++ codebase,
.. include:: ../reusables/beta-note-package-management.rst
To run an existing CodeQL query pack from the GitHub Container registry, you need to download it first::
codeql pack download microsoft/coding-standards@1.0.0
Afterwards, you can run the pack on a specific database::
codeql database analyze <database> microsoft/coding-standards@1.0.0 <scope>/<other-pack> --format=sarifv2.1.0 --output=query-results.sarif
The ``analyze`` command above runs the default suite from ``microsoft/coding-standards v1.0.0`` and the latest version of ``scope/other-pack`` on the specified database.
For further information about default suites, see ":ref:`Publishing and using CodeQL packs <publishing-and-using-codeql-packs>`".
Running query suites
~~~~~~~~~~~~~~~~~~~~
To run a query suite over a CodeQL database for a C/C++ codebase,
you could use the following command from the directory containing your database::
codeql database analyze <cpp-database> cpp-code-scanning.qls --format=sarifv2.1.0 --output=cpp-results.sarif
The analysis generates a file in the v2.1.0 SARIF format that is supported by all versions of GitHub.
This file can be uploaded to GitHub using ``github upload-results`` or the code scanning API.
For more information, see `Analyzing a CodeQL database <https://docs.github.com/en/code-security/secure-coding/configuring-codeql-cli-in-your-ci-system#analyzing-a-codeql-database>`__
This file can be uploaded to GitHub by executing ``codeql github upload-results`` or the code scanning API.
For more information, see `Analyzing a CodeQL database <https://docs.github.com/en/code-security/secure-coding/configuring-codeql-cli-in-your-ci-system#analyzing-a-codeql-database>`__
or `Code scanning API <https://docs.github.com/en/rest/reference/code-scanning>`__ in the GitHub documentation.
CodeQL query suites are ``.qls`` files that use directives to select queries to run
CodeQL query suites are ``.qls`` files that use directives to select queries to run
based on certain metadata properties. The standard QL packs have metadata that specify
the location of the code scanning suites, so the CodeQL CLI knows where to find these
the location of the query suites used by code scanning, so the CodeQL CLI knows where to find these
suite files automatically, and you don't have to specify the full path on the command line.
For more information, see ":ref:`About QL packs <standard-ql-packs>`."
@@ -137,7 +153,7 @@ and at the following path in the CodeQL for Go repository::
ql/src/codeql-suites/go-code-scanning.qls
The repository also includes the query suites used by `LGTM.com <https://lgtm.com>`__.
These are stored alongside the code scanning suites with names of the form: ``<language>-lgtm.qls``.
These are stored alongside the query suites for code scanning with names of the form: ``<language>-lgtm.qls``.
For information about creating custom query suites, see ":doc:`Creating
CodeQL query suites <creating-codeql-query-suites>`."
@@ -149,12 +165,21 @@ When you create a CodeQL database, the extractor stores diagnostic data in the d
If the analysis found fewer results for standard queries than you expected, review the results of the diagnostic and summary queries to check whether the CodeQL database is likely to be a good representation of the codebase that you want to analyze.
Integrating a CodeQL pack into a code scanning workflow in GitHub
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. include:: ../reusables/beta-note-package-management.rst
You can use CodeQL query packs in your Code Scanning setup. This allows you to select query packs published by various sources and use them to analyze your code.
For more information, see "`Using CodeQL query packs in the CodeQL action <https://docs.github.com/en/code-security/secure-coding/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-codeql-query-packs/>`_" or "`Downloading and using CodeQL query packs in your CI system <https://docs.github.com/en/code-security/secure-coding/using-codeql-code-scanning-with-your-existing-ci-system/configuring-codeql-cli-in-your-ci-system#downloading-and-using-codeql-query-packs>`_."
Running all queries in a directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can run all the queries located in a directory by providing the directory
path, rather than listing all the individual query files. Paths are searched
recursively, so any queries contained in subfolders will also be executed.
recursively, so any queries contained in subfolders will also be executed.
.. pull-quote::
@@ -164,12 +189,12 @@ recursively, so any queries contained in subfolders will also be executed.
<about-ql-packs>` when executing ``database analyze``
as it contains some special queries that aren't designed to be used with
the command. Rather, to run a wide range of useful queries, run one of the
LGTM.com query suites.
LGTM.com query suites.
For example, to execute all Python queries contained in the ``Functions``
directory you would run::
codeql database analyze <python-database> ../ql/python/ql/src/Functions/ --format=sarif-latest --output=python-analysis/python-results.sarif
codeql database analyze <python-database> ../ql/python/ql/src/Functions/ --format=sarif-latest --output=python-analysis/python-results.sarif
A SARIF results file is generated. Specifying ``--format=sarif-latest`` ensures
that the results are formatted according to the most recent SARIF specification
@@ -196,19 +221,19 @@ corresponds to an alert. Each line is a comma-separated list with the following
- Description
- Example
* - Name
- Name of the query that identified the result.
- Name of the query that identified the result.
- ``Inefficient regular expression``
* - Description
- Description of the query.
- ``A regular expression that requires exponential time to match certain
inputs can be a performance bottleneck, and may be vulnerable to
- Description of the query.
- ``A regular expression that requires exponential time to match certain
inputs can be a performance bottleneck, and may be vulnerable to
denial-of-service attacks.``
* - Severity
- Severity of the query.
- ``error``
* - Message
- Alert message.
- ``This part of the regular expression may cause exponential backtracking
- Alert message.
- ``This part of the regular expression may cause exponential backtracking
on strings containing many repetitions of '\\\\'.``
* - Path
- Path of the file containing the alert.
@@ -225,14 +250,14 @@ corresponds to an alert. Each line is a comma-separated list with the following
included when the same value as the start line.
- ``64``
* - End column
- Where available, the column of the end line that marks the end of the
- Where available, the column of the end line that marks the end of the
alert code. Otherwise the end line is repeated.
- ``617``
Results files can be integrated into your own code-review or debugging
infrastructure. For example, SARIF file output can be used to highlight alerts
in the correct location in your source code using a SARIF viewer plugin for your
IDE.
IDE.
Further reading
---------------

View File

@@ -9,12 +9,14 @@ Learn more about the files you can use when running CodeQL processes and the res
:titlesonly:
:hidden:
about-codeql-packs
about-ql-packs
query-reference-files
sarif-output
exit-codes
- :doc:`About CodeQL packs <about-codeql-packs>`: CodeQL packs are created with the CodeQL CLI and are used to create, depend on, publish, and run CodeQL queries and libraries.
- :doc:`About QL packs <about-ql-packs>`: QL packs are used to organize the files used in CodeQL analysis. They
contain queries, library files, query suites, and important metadata.
- :doc:`Query reference files <query-reference-files>`: A query reference file is text file that defines the location of one query to test.

View File

@@ -0,0 +1,70 @@
.. _creating-and-working-with-codeql-packs:
Creating and working with CodeQL packs
======================================
You can use CodeQL packs to create, share, depend on, and run CodeQL queries and libraries.
.. include:: ../reusables/beta-note-package-management.rst
About CodeQL packs and the CodeQL CLI
-------------------------------------
With CodeQL packs and the package management commands in the CodeQL CLI, you can publish your custom queries and integrate them into your codebase analysis.
There are two types of CodeQL packs: query packs and library packs.
* Query packs are designed to be run. When a query pack is published, the bundle includes all the transitive dependencies and a compilation cache. This ensures consistent and efficient execution of the queries in the pack.
* Library packs are designed to be used by query packs (or other library packs) and do not contain queries themselves. The libraries are not compiled and there is no compilation cache included when the pack is published.
You can use the ``pack`` command in the CodeQL CLI to create CodeQL packs, add dependencies to packs, and install or update dependencies. You can also publish and download CodeQL packs using the ``pack`` command. For more information, see ":doc:`Publishing and using CodeQL packs <publishing-and-using-codeql-packs>`."
Creating a CodeQL pack
----------------------
You can create a CodeQL pack by running the following command from the checkout root of your project:
::
codeql pack init <scope>/<pack>
You must specify:
- ``<scope>``: the name of the GitHub organization or user account that you will publish to.
- ``<pack>``: the name for the pack that you are creating.
The ``codeql pack init`` command creates the directory structure and configuration files for a CodeQL pack. By default, the command creates a query pack. If you want to create a library pack, you must edit the ``qlpack.yml`` file to explicitly declare the file as a library pack by including the ``library:true`` property.
Modifying an existing QL pack to create a CodeQL pack
-----------------------------------------------------
If you already have a ``qlpack.yml`` file, you can edit it manually to convert it into a CodeQL pack.
#. Edit the ``name`` property so that it matches the format ``<scope>/<name>``, where ``<scope>`` is the name of the GitHub organization or user account that you will publish to.
#. In the ``qlpack.yml`` file, include a ``version`` property with a semver identifier, as well as an optional ``dependencies`` block.
For more information about the properties, see ":ref:`About CodeQL packs <about-codeql-packs>`."
Adding and installing dependencies to a CodeQL pack
---------------------------------------------------
You can add dependencies on CodeQL packs using the command ``codeql pack add``. You must specify the scope, name, and version range.
::
codeql pack add <scope>/<name>@x.x.x <scope>/<other-name>
The version range is optional. If you leave off the version range, the latest version will be added. Otherwise, the latest version that satisfies the requested range will be added.
This command updates the ``qlpack.yml`` file with the requested dependencies and downloads them into the package cache. Please note that this command will reformat the file and remove all comments.
You can also manually edit the ``qlpack.yml`` file to include dependencies and install the dependencies with the command:
::
codeql pack install
This command downloads all dependencies to the shared cache on the local disk.
.. pull-quote::
Note
Running the ``codeql pack add`` and ``codeql pack install`` commands will generate or update the ``qlpack.lock.yml`` file. This file should be checked-in to version control. The ``qlpack.lock.yml`` file contains the precise version numbers used by the pack.

View File

@@ -0,0 +1,74 @@
.. _publishing-and-using-codeql-packs:
Publishing and using CodeQL packs
=================================
You can publish your own CodeQL packs and use packs published by other people.
.. include:: ../reusables/beta-note-package-management.rst
Configuring the ``qlpack.yml`` file before publishing
-----------------------------------------------------
You can check and modify the configuration details of your CodeQL pack prior to publishing. Open the ``qlpack.yml`` file in your preferred text editor.
.. code-block:: none
library: # set to true if the pack is a library. Set to false or omit for a query pack
name: <scope>/<pack>
version: <x.x.x>
description: <Description to publish with the package>
default-suite: # optional, one or more queries in the pack to run by default
- query: <relative-path>/query-file>.ql
default-suite-file: default-queries.qls # optional, a pointer to a query-suite in this pack
license: # optional, the license under which the pack is published
dependencies: # map from CodeQL pack name to version range
- ``name:`` must follow the <scope>/<pack> format, where <scope> is the GitHub organization that you will publish to and <pack> is the name for the pack.
- A maximum of one of ``default-suite`` or ``default-suite-file`` is allowed. These are two different ways to define a default query suite to be run, the first by specifying queries directly in the `qlpack.yml` file and the second by specifying a query suite in the pack.
Running ``codeql pack publish``
-------------------------------
When you are ready to publish a pack to the GitHub Container registry, you can run the following command in the root of the pack directory:
::
codeql pack publish
The published package will be displayed in the packages section of GitHub organization specified by the scope in the ``qlpack.yml`` file.
Running ``codeql pack download <scope>/<pack>``
-----------------------------------------------
To run a pack that someone else has created, you must first download it by running the following command:
::
codeql pack download <scope>/<pack>@x.x.x
- ``<scope>``: the name of the GitHub organization that you will download from.
- ``<pack>``: the name for the pack that you want to download.
- ``@x.x.x``: an optional version number. If omitted, the latest version will be downloaded.
This command accepts arguments for multiple packs.
Using a CodeQL pack to analyze a CodeQL database
------------------------------------------------
To analyze a CodeQL database with a CodeQL pack, run the following command:
::
codeql database analyze <database> <scope>/<pack>@x.x.x
- ``<database>``: the CodeQL database to be analyzed.
- ``<scope>``: the name of the GitHub organization that the pack is published to.
- ``<pack>``: the name for the pack that you are using.
- ``@x.x.x``: an optional version number. If omitted, the latest version will be used.
The ``analyze`` command will run the default suite of any specified CodeQL packs. You can specify multiple CodeQL packs to be used for analyzing a CodeQL database. For example:
::
codeql <database> analyze <scope>/<pack> <scope>/<other-pack>

View File

@@ -8,7 +8,7 @@ specific vulnerabilities or errors.
This topic is specifically about writing
queries to use with the `database analyze <../manual/database-analyze>`__
command to produce :ref:`interpreted results <interpret-query-results>`.
command to produce :ref:`interpreted results <interpret-query-results>`.
.. include:: ../reusables/advanced-query-execution.rst
@@ -26,16 +26,16 @@ Including query metadata
------------------------
Query metadata is included at the top of each query file. It provides users with information about
the query, and tells the CodeQL CLI how to process the query results.
the query, and tells the CodeQL CLI how to process the query results.
When running queries with the ``database analyze`` command, you must include the
following two properties to ensure that the results are interpreted correctly:
- Query identifier (``@id``): a sequence of words composed of lowercase letters or
digits, delimited by ``/`` or ``-``, identifying and classifying the query.
digits, delimited by ``/`` or ``-``, identifying and classifying the query.
- Query type (``@kind``): identifies the query as a simple alert (``@kind problem``),
an alert documented by a sequence of code locations (``@kind path-problem``),
for extractor troubleshooting (``@kind diagnostic``), or a summary metric
an alert documented by a sequence of code locations (``@kind path-problem``),
for extractor troubleshooting (``@kind diagnostic``), or a summary metric
(``@kind metric`` and ``@tags summary``).
For more information about these metadata properties, see ":ref:`Metadata for CodeQL queries
@@ -47,22 +47,30 @@ For more information about these metadata properties, see ":ref:`Metadata for Co
Metadata requirements may differ if you want to use your query with other
applications. For more information, see ":ref:`Metadata for CodeQL queries
<metadata-for-codeql-queries>`
."
."
Packaging custom QL queries
---------------------------
Creating a custom QL pack
-------------------------
.. include:: ../reusables/beta-note-package-management.rst
When writing your own queries, you should save them in a custom QL pack
directory. QL packs provide a way of organizing
the files used in CodeQL analysis. This directory must contain a file
named ``qlpack.yml`` at the root. Your custom queries should be saved in the QL
pack root, or its subdirectories.
When you write your own queries, you should save them in a custom QL pack
directory. When you are ready to share your queries with other users, you can publish the pack as a CodeQL pack to GitHub Packages - the GitHub Container registry.
QL packs organize the files used in CodeQL analysis and can store queries,
library files, query suites, and important metadata. Their root directory must
contain a file named ``qlpack.yml``. Your custom queries should be saved in the
QL pack root, or its subdirectories.
For each QL pack, the ``qlpack.yml`` file includes information that tells CodeQL
how to compile the queries, what libraries the pack depends on, and where to find
query suite definitions. For more information about what to include in this
file, see ":ref:`About QL packs <custom-ql-packs>`."
how to compile the queries, which other CodeQL packs and libraries the pack
depends on, and where to find query suite definitions. For more information
about what to include in this file, see ":ref:`About QL packs <about-ql-packs>`."
CodeQL packages are used to create, share, depend on, and run CodeQL queries and
libraries. You can publish your own CodeQL packages and download ones created by
others via the the Container registry. For further information see
":ref:`About CodeQL packs <about-codeql-packs>`."
Contributing to the CodeQL repository
-------------------------------------

View File

@@ -41,6 +41,12 @@ See the following links to learn how to get set up and run CodeQL commands:
Test query help files by rendering them as markdown to ensure they are valid
before adding them to the CodeQL repository or using them in code scanning.
- :doc:`Creating and working with CodeQL packs <creating-and-working-with-codeql-packs>`:
Create, share, depend on, and run CodeQL queries and libraries.
- :doc:`Publishing and using CodeQL packs <publishing-and-using-codeql-packs>`:
Publish your own or use others CodeQL packs for code scanning.
- :doc:`Specifying command options in a CodeQL configuration file <specifying-command-options-in-a-codeql-configuration-file>`:
You can save default or frequently used options for your commands in a per-user configuration file.
@@ -57,4 +63,6 @@ See the following links to learn how to get set up and run CodeQL commands:
creating-codeql-query-suites
testing-custom-queries
testing-query-help-files
creating-and-working-with-codeql-packs
publishing-and-using-codeql-packs
Specifying command options <specifying-command-options-in-a-codeql-configuration-file>

View File

@@ -497,6 +497,7 @@ The following sequences of characters are keyword tokens:
max
min
module
newtype
none
not
or
@@ -514,6 +515,7 @@ The following sequences of characters are keyword tokens:
then
this
true
unique
where
Operators

View File

@@ -57,7 +57,7 @@ As an aside, note that the following query leads to a compile-time error:
select i
In theory, it would have infinitely many results, as the variable ``i`` is not constrained to a
finite number of possible values. For more informaion, see ":ref:`binding`."
finite number of possible values. For more information, see ":ref:`binding`."
.. index:: variable; free, variable; bound
.. _free-variables:

View File

@@ -0,0 +1,5 @@
.. pull-quote::
Note
The CodeQL package management functionality, including CodeQL packs, is currently available as a beta release and is subject to change. During the beta release, CodeQL packs are available only using GitHub Packages - the GitHub Container registry. To use this beta functionality, install the beta release of the CodeQL CLI bundle from: https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.6.0-beta.1.

View File

@@ -95,6 +95,7 @@ Java built-in support
Guava, Utility and collections library
Hibernate, Database
iBatis / MyBatis, Database
Jackson, Serialization
Java Persistence API (JPA), Database
JDBC, Database
Protobuf, Serialization

View File

@@ -45,10 +45,12 @@ The following properties are supported by all query files:
| | | ``high`` | |
| | | ``very-high`` | |
+-----------------------+---------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``@problem.severity`` | | ``error`` | Defines the level of severity of any alerts generated by the query. This, along with the ``@precision`` property, determines whether the results are displayed by default on LGTM. |
| ``@problem.severity`` | | ``error`` | Defines the level of severity of any alerts generated by a non-security query. This, along with the ``@precision`` property, determines whether the results are displayed by default on LGTM. |
| | | ``warning`` | |
| | | ``recommendation`` | |
+-----------------------+---------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``@security-severity``| ``<score>`` | Defines the level of severity, between 0.0 and 10.0, for queries with `@tags security`. For more information about calculating `@security-severity`, see the [GitHub changelog](https://github.blog/changelog/2021-07-19-codeql-code-scanning-new-severity-levels-for-security-alerts/). |
+-----------------------+---------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Additional properties for filter queries
----------------------------------------

View File

@@ -115,10 +115,11 @@ Alert queries (`@kind problem` or `path-problem`) support two further properties
* `medium`
* `high`
* `very-high`
* `@problem.severity`defines the level of severity of the alert:
* `@problem.severity`defines the level of severity of non-security alerts:
* `error`an issue that is likely to cause incorrect program behavior, for example a crash or vulnerability.
* `warning`an issue that indicates a potential problem in the code, or makes the code fragile if another (unrelated) part of code is changed.
* `recommendation`an issue where the code behaves correctly, but it could be improved.
* `@security-severity`-defines the level of severity, between 0.0 and 10.0, for queries with `@tags security`. For more information about calculating `@security-severity`, see the [GitHub changelog](https://github.blog/changelog/2021-07-19-codeql-code-scanning-new-severity-levels-for-security-alerts/).
The values of `@precision` and `@problem.severity` assigned to a query that is part of the standard set determine how the results are displayed by LGTM. See [About alerts](https://help.semmle.com/lgtm-enterprise/user/help/about-alerts.html) and [Alert interest](https://lgtm.com/help/lgtm/alert-interest) for further information. For information about using custom queries in LGTM on a 'per-project' basis, see [Writing custom queries to include in LGTM analysis](https://lgtm.com/help/lgtm/writing-custom-queries) and [About adding custom queries](https://help.semmle.com/lgtm-enterprise/admin/help/about-adding-custom-queries.html).
@@ -165,6 +166,8 @@ When you tag a query like this, the associated CWE pages from [MITRE.org](https:
Code Scanning may use tags to identify queries with specific meanings across languages. Currently, there is only one such tag: `lines-of-code`. The sum of the results for queries with this tag that return a single number column ([example for JavaScript](https://github.com/github/codeql/blob/c47d680d65f09a851e41d4edad58ffa7486b5431/java/ql/src/Metrics/Summaries/LinesOfCode.ql)) is interpreted by Code Scanning as the lines of code under the source root present in the database. Each language should have exactly one query of this form.
Maintainers are expected to add a `@security-severity` tag to security relevant queries that will be run on Code Scanning. There is a documented internal process for generating these `@security-severity` values.
## QL area
### Alert messages

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The "Deserialization of user-controlled data" (`java/unsafe-deserialization`) query
now recognizes `Jackson` deserialization.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Added additional taint steps modeling constructors for collections in `java.util`.

View File

@@ -18,7 +18,7 @@ java.lang,,,3,,,,,,,,,,,,,,,1,2
java.net,10,3,6,,,,,,,10,,,,,,,3,6,
java.nio,10,,2,,10,,,,,,,,,,,,,2,
java.sql,7,,,,,,,,,,,7,,,,,,,
java.util,,,295,,,,,,,,,,,,,,,15,280
java.util,,,332,,,,,,,,,,,,,,,15,317
javax.json,,,123,,,,,,,,,,,,,,,100,23
javax.naming.directory,1,,,,,,,,1,,,,,,,,,,
javax.net.ssl,2,,,,,,,,,,2,,,,,,,,
1 package sink source summary sink:bean-validation sink:create-file sink:header-splitting sink:information-leak sink:jexl sink:ldap sink:open-url sink:set-hostname-verifier sink:sql sink:url-open-stream sink:url-redirect sink:xpath sink:xss source:remote summary:taint summary:value
18 java.net 10 3 6 10 3 6
19 java.nio 10 2 10 2
20 java.sql 7 7
21 java.util 295 332 15 280 317
22 javax.json 123 100 23
23 javax.naming.directory 1 1
24 javax.net.ssl 2 2

View File

@@ -14,9 +14,9 @@ Java framework & library support
`Apache Commons Text <https://commons.apache.org/proper/commons-text/>`_,``org.apache.commons.text``,,272,,,,,,,,
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,136,28,,,3,,,,25
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,158,6,,6,,,,,
Java Standard Library,``java.*``,3,327,30,13,,,7,,,10
Java Standard Library,``java.*``,3,364,30,13,,,7,,,10
Java extensions,"``javax.*``, ``jakarta.*``",22,540,18,,,,,1,1,2
`Spring <https://spring.io/>`_,``org.springframework.*``,29,306,62,,,,19,14,,29
Others,"``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.databind``, ``com.unboundid.ldap.sdk``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.dom4j``, ``org.hibernate``, ``org.jooq``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``",7,12,82,,,,14,18,,
Totals,,84,2428,296,13,6,6,107,33,1,66
Totals,,84,2465,296,13,6,6,107,33,1,66

View File

@@ -17,7 +17,7 @@ from InstanceOfExpr ioe, RefType t, RefType ct
where
ioe.getExpr() instanceof ThisAccess and
t = ioe.getExpr().getType() and
ct = ioe.getTypeName().getType() and
ct = ioe.getCheckedType() and
ct.getASupertype*() = t
select ioe,
"Testing whether 'this' is an instance of $@ in $@ introduces a dependency cycle between the two types.",

View File

@@ -15,7 +15,7 @@ import java
from InstanceOfExpr ioe, RefType t, RefType ct
where
t = ioe.getExpr().getType() and
ct = ioe.getTypeName().getType() and
ct = ioe.getCheckedType() and
ct = t.getASupertype+()
select ioe,
"There is no need to test whether an instance of $@ is also an instance of $@ - it always is.", t,

View File

@@ -17,8 +17,8 @@ predicate instanceofInEquals(EqualsMethod m, InstanceOfExpr e) {
m.fromSource() and
e.getEnclosingCallable() = m and
e.getExpr().(VarAccess).getVariable() = m.getParameter() and
exists(Class instanceofType |
instanceofType = e.getTypeName().getType() and
exists(RefType instanceofType |
instanceofType = e.getCheckedType() and
not instanceofType.isFinal()
)
}

View File

@@ -51,7 +51,7 @@ class LockObjectField extends Field {
class ValidSynchStmt extends Stmt {
ValidSynchStmt() {
// It's OK to lock the enclosing class.
this.(SynchronizedStmt).getExpr().(TypeLiteral).getTypeName().getType() =
this.(SynchronizedStmt).getExpr().(TypeLiteral).getReferencedType() =
this.getEnclosingCallable().getDeclaringType()
or
// It's OK to lock on a "lock object field".

View File

@@ -24,7 +24,7 @@ import java
predicate isSynchronizedByBlock(Method m) {
exists(SynchronizedStmt sync, Expr on | sync = m.getBody().getAChild*() and on = sync.getExpr() |
if m.isStatic()
then on.(TypeLiteral).getTypeName().getType() = m.getDeclaringType()
then on.(TypeLiteral).getReferencedType() = m.getDeclaringType()
else on.(ThisAccess).getType().(RefType).getSourceDeclaration() = m.getDeclaringType()
)
}

View File

@@ -18,7 +18,7 @@ import semmle.code.java.dataflow.SSA
/** `ioe` is of the form `va instanceof t`. */
predicate instanceOfCheck(InstanceOfExpr ioe, VarAccess va, RefType t) {
ioe.getExpr() = va and
ioe.getTypeName().getType().(RefType).getSourceDeclaration() = t
ioe.getCheckedType().getSourceDeclaration() = t
}
/** Expression `e` assumes that `va` could be of type `t`. */

View File

@@ -127,7 +127,9 @@ predicate potentiallyStatic(InnerClass c) {
forall(InnerClass superOfNested | superOfNested = nested.getASourceSupertype+() |
potentiallyStatic(superOfNested)
)
)
) and
// JUnit Nested test classes are required to be non-static.
not c.hasAnnotation("org.junit.jupiter.api", "Nested")
}
/**

View File

@@ -14,8 +14,8 @@ may have unforeseen effects, such as the execution of arbitrary code.
</p>
<p>
There are many different serialization frameworks. This query currently
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap
and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap,
Jackson and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
</p>
</overview>
@@ -91,6 +91,15 @@ Remote code execution in JYaml library:
JsonIO deserialization vulnerabilities:
<a href="https://klezvirus.github.io/Advanced-Web-Hacking/Serialisation/">JsonIO deserialization</a>.
</li>
<li>
Research by Moritz Bechler:
<a href="https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true">Java Unmarshaller Security - Turning your data into code execution</a>
</li>
<li>
Blog posts by the developer of Jackson libraries:
<a href="https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062">On Jackson CVEs: Dont Panic — Here is what you need to know</a>
<a href="https://cowtowncoder.medium.com/jackson-2-10-safe-default-typing-2d018f0ce2ba">Jackson 2.10: Safe Default Typing</a>
</li>
</references>
</qhelp>

View File

@@ -12,51 +12,9 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UnsafeDeserialization
import semmle.code.java.security.UnsafeDeserializationQuery
import DataFlow::PathGraph
class UnsafeDeserializationConfig extends TaintTracking::Configuration {
UnsafeDeserializationConfig() { this = "UnsafeDeserializationConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassInstanceExpr cie |
cie.getArgument(0) = pred.asExpr() and
cie = succ.asExpr() and
(
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader or
cie.getConstructor().getDeclaringType() instanceof YamlBeansReader or
cie.getConstructor().getDeclaringType().getASupertype*() instanceof UnsafeHessianInput or
cie.getConstructor().getDeclaringType() instanceof BurlapInput
)
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof BurlapInputInitMethod and
ma.getArgument(0) = pred.asExpr() and
ma.getQualifier() = succ.asExpr()
)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ClassInstanceExpr cie |
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader and
cie = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(cie.getArgument(1)))
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
ma.getArgument(0) = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(ma.getArgument(1)))
)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeDeserializationConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode().(UnsafeDeserializationSink).getMethodAccess(), source, sink,

View File

@@ -16,9 +16,9 @@ import java
import semmle.code.java.Collections
predicate guardedByInstanceOf(VarAccess e, RefType t) {
exists(IfStmt s, InstanceOfExpr instanceCheck, Type checkType |
exists(IfStmt s, InstanceOfExpr instanceCheck, RefType checkType |
s.getCondition() = instanceCheck and
instanceCheck.getTypeName().getType() = checkType and
instanceCheck.getCheckedType() = checkType and
// The same variable appears as the subject of the `instanceof`.
instanceCheck.getExpr() = e.getVariable().getAnAccess() and
// The checked type is either the type itself, or a raw version. For example, it is usually

View File

@@ -26,7 +26,7 @@ all certificate errors are ignored. In the 'GOOD' case, certificate errors are r
<references>
<li>Teamdev:
<a href="https://jxbrowser.support.teamdev.com/support/discussions/topics/9000051708">
<a href="https://jxbrowser-support.teamdev.com/release-notes/2019/v6-24.html">
Changelog of JxBrowser 6.24</a>.</li>
</references>
</qhelp>

View File

@@ -34,6 +34,29 @@ public class SpringUrlRedirect {
}
@GetMapping("url5")
public ResponseEntity<Void> bad5(String redirectUrl) {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(redirectUrl))
.build();
}
@GetMapping("url6")
public ResponseEntity<Void> bad6(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(URI.create(redirectUrl));
return new ResponseEntity<>(httpHeaders, HttpStatus.SEE_OTHER);
}
@GetMapping("url7")
public ResponseEntity<Void> bad7(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Location", redirectUrl);
return ResponseEntity.status(HttpStatus.SEE_OTHER).headers(httpHeaders).build();
}
@GetMapping("url8")
public RedirectView good1(String redirectUrl) {
RedirectView rv = new RedirectView();
if (redirectUrl.startsWith(VALID_REDIRECT)){

View File

@@ -21,10 +21,10 @@ redirects on the server; then choose from that list based on the user input prov
<example>
<p>The following examples show the bad case and the good case respectively.
In <code>bad1</code> method and <code>bad2</code> method and <code>bad3</code> method and
<code>bad4</code> method, shows an HTTP request parameter being used directly in a URL redirect
without validating the input, which facilitates phishing attacks. In <code>good1</code> method,
shows how to solve this problem by verifying whether the user input is a known fixed string beginning.
The <code>bad</code> methods show an HTTP request parameter being used directly
in a URL redirect without validating the input, which facilitates phishing attacks.
In the <code>good1</code> method, it is shown how to solve this problem by verifying whether
the user input is a known fixed string beginning.
</p>
<sample src="SpringUrlRedirect.java" />
@@ -33,5 +33,6 @@ shows how to solve this problem by verifying whether the user input is a known f
<references>
<li>A Guide To Spring Redirects: <a href="https://www.baeldung.com/spring-redirect-and-forward">Spring Redirects</a>.</li>
<li>Url redirection - attack and defense: <a href="https://www.virtuesecurity.com/kb/url-redirection-attack-and-defense/">Url Redirection</a>.</li>
<li>How to redirect to an external URL from Spring Boot REST Controller (Post/Redirect/Get pattern)?: <a href="https://fullstackdeveloper.guru/2021/03/12/how-to-redirect-to-an-external-url-from-spring-boot-rest-controller/">ResponseEntity Redirection</a>.</li>
</references>
</qhelp>

View File

@@ -34,6 +34,10 @@ class SpringUrlRedirectFlowConfig extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof SpringUrlRedirectSink }
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
springUrlRedirectTaintStep(fromNode, toNode)
}
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof StartsWithSanitizer
}
@@ -57,6 +61,8 @@ class SpringUrlRedirectFlowConfig extends TaintTracking::Configuration {
not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().regexpMatch("^%s.*")
)
)
or
nonLocationHeaderSanitizer(node)
}
}

View File

@@ -35,8 +35,13 @@ class RedirectAppendCall extends MethodAccess {
}
/** A URL redirection sink from spring controller method. */
class SpringUrlRedirectSink extends DataFlow::Node {
SpringUrlRedirectSink() {
abstract class SpringUrlRedirectSink extends DataFlow::Node { }
/**
* A sink for URL Redirection via the Spring View classes.
*/
private class SpringViewUrlRedirectSink extends SpringUrlRedirectSink {
SpringViewUrlRedirectSink() {
exists(RedirectBuilderExpr rbe |
rbe.getRightOperand() = this.asExpr() and
any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable())
@@ -71,3 +76,64 @@ class SpringUrlRedirectSink extends DataFlow::Node {
)
}
}
/**
* A sink for URL Redirection via the `ResponseEntity` class.
*/
private class SpringResponseEntityUrlRedirectSink extends SpringUrlRedirectSink {
SpringResponseEntityUrlRedirectSink() {
// Find `new ResponseEntity(httpHeaders, ...)` or
// `new ResponseEntity(..., httpHeaders, ...)` sinks
exists(ClassInstanceExpr cie, Argument argument |
cie.getConstructedType() instanceof SpringResponseEntity and
argument.getType() instanceof SpringHttpHeaders and
argument = cie.getArgument([0, 1]) and
this.asExpr() = argument
)
or
// Find `ResponseEntity.status(...).headers(taintHeaders).build()` or
// `ResponseEntity.status(...).location(URI.create(taintURL)).build()` sinks
exists(MethodAccess ma |
ma.getMethod()
.getDeclaringType()
.hasQualifiedName("org.springframework.http", "ResponseEntity$HeadersBuilder<BodyBuilder>") and
ma.getMethod().getName() in ["headers", "location"] and
this.asExpr() = ma.getArgument(0)
)
}
}
private class HttpHeadersMethodAccess extends MethodAccess {
HttpHeadersMethodAccess() { this.getMethod().getDeclaringType() instanceof SpringHttpHeaders }
}
private class HttpHeadersAddSetMethodAccess extends HttpHeadersMethodAccess {
HttpHeadersAddSetMethodAccess() { this.getMethod().getName() in ["add", "set"] }
}
private class HttpHeadersSetLocationMethodAccess extends HttpHeadersMethodAccess {
HttpHeadersSetLocationMethodAccess() { this.getMethod().hasName("setLocation") }
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step from a tainted argument to
* a `HttpHeaders` instance qualifier, i.e. `httpHeaders.setLocation(tainted)`.
*/
predicate springUrlRedirectTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(HttpHeadersSetLocationMethodAccess ma |
fromNode.asExpr() = ma.getArgument(0) and
toNode.asExpr() = ma.getQualifier()
)
}
/**
* A sanitizer to exclude the cases where the `HttpHeaders.add` or `HttpHeaders.set`
* methods are called with a HTTP header other than "Location".
* E.g: `httpHeaders.add("X-Some-Header", taintedUrlString)`
*/
predicate nonLocationHeaderSanitizer(DataFlow::Node node) {
exists(HttpHeadersAddSetMethodAccess ma, Argument firstArg | node.asExpr() = ma.getArgument(1) |
firstArg = ma.getArgument(0) and
not firstArg.(CompileTimeConstantExpr).getStringValue().matches("Location")
)
}

View File

@@ -35,6 +35,7 @@ predicate instantiates(RefType t, GenericType g, int i, RefType arg) {
* - a class `MyIntMap<V> extends HashMap<Integer, V>` instantiates `Map` (among others)
* with the `0`-th type parameter being `Integer` and the `1`-th type parameter being `V`.
*/
pragma[nomagic]
predicate indirectlyInstantiates(RefType t, GenericType g, int i, RefType arg) {
instantiates(t, g, i, arg)
or

View File

@@ -62,7 +62,7 @@ predicate depends(RefType t, RefType dep) {
or
// the type of a type literal accessed in `t`,
exists(TypeLiteral l | l.getEnclosingCallable().getDeclaringType() = t |
usesType(l.getTypeName().getType(), dep)
usesType(l.getReferencedType(), dep)
)
or
// the type of an annotation (or one of its element values) that annotates `t` or one of its members,
@@ -76,7 +76,7 @@ predicate depends(RefType t, RefType dep) {
or
// the type accessed in an `instanceof` expression in `t`.
exists(InstanceOfExpr ioe | t = ioe.getEnclosingCallable().getDeclaringType() |
usesType(ioe.getTypeName().getType(), dep)
usesType(ioe.getCheckedType(), dep)
)
)
}

View File

@@ -80,7 +80,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
elem = l and
l.getEnclosingCallable().getDeclaringType() = t
|
usesType(l.getTypeName().getType(), dep)
usesType(l.getReferencedType(), dep)
)
or
// the type of an annotation (or one of its element values) that annotates `t` or one of its members,
@@ -100,7 +100,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
elem = ioe and
t = ioe.getEnclosingCallable().getDeclaringType()
|
usesType(ioe.getTypeName().getType(), dep)
usesType(ioe.getCheckedType(), dep)
)
)
}

View File

@@ -46,17 +46,6 @@ class Expr extends ExprParent, @expr {
*/
int getKind() { exprs(this, result, _, _, _) }
/**
* DEPRECATED: This is no longer necessary. See `Expr.isParenthesized()`.
*
* Gets this expression with any surrounding parentheses removed.
*/
deprecated Expr getProperExpr() {
result = this.(ParExpr).getExpr().getProperExpr()
or
result = this and not this instanceof ParExpr
}
/** Gets the statement containing this expression, if any. */
Stmt getEnclosingStmt() { statementEnclosingExpr(this, result) }
@@ -1318,19 +1307,6 @@ class SwitchExpr extends Expr, StmtParent, @switchexpr {
override string getAPrimaryQlClass() { result = "SwitchExpr" }
}
/**
* DEPRECATED: Use `Expr.isParenthesized()` instead.
*
* A parenthesised expression.
*/
deprecated class ParExpr extends Expr, @parexpr {
/** Gets the expression inside the parentheses. */
deprecated Expr getExpr() { result.getParent() = this }
/** Gets a printable representation of this expression. */
override string toString() { result = "(...)" }
}
/** An `instanceof` expression. */
class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the expression on the left-hand side of the `instanceof` operator. */
@@ -1357,6 +1333,9 @@ class InstanceOfExpr extends Expr, @instanceofexpr {
/** Gets the access to the type on the right-hand side of the `instanceof` operator. */
Expr getTypeName() { result.isNthChildOf(this, 1) }
/** Gets the type this `instanceof` expression checks for. */
RefType getCheckedType() { result = getTypeName().getType() }
/** Gets a printable representation of this expression. */
override string toString() { result = "...instanceof..." }
@@ -1449,6 +1428,12 @@ class TypeLiteral extends Expr, @typeliteral {
/** Gets the access to the type whose class is accessed. */
Expr getTypeName() { result.getParent() = this }
/**
* Gets the type this type literal refers to. For example for `String.class` the
* result is the type representing `String`.
*/
Type getReferencedType() { result = getTypeName().getType() }
/** Gets a printable representation of this expression. */
override string toString() { result = this.getTypeName().toString() + ".class" }

View File

@@ -52,7 +52,7 @@ abstract private class ReflectiveClassIdentifier extends Expr {
private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral {
override RefType getReflectivelyIdentifiedClass() {
result = getTypeName().getType().(RefType).getSourceDeclaration()
result = getReferencedType().(RefType).getSourceDeclaration()
}
}

View File

@@ -168,7 +168,7 @@ class TestNGTestMethod extends Method {
or
// Or the data provider class should be declared
result.getDeclaringType() =
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getTypeName().getType()
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getReferencedType()
)
}
}
@@ -227,7 +227,7 @@ class TestNGListenersAnnotation extends TestNGAnnotation {
* Gets a listener defined in this annotation.
*/
TestNGListenerImpl getAListener() {
result = getAValue("value").(TypeLiteral).getTypeName().getType()
result = getAValue("value").(TypeLiteral).getReferencedType()
}
}
@@ -303,7 +303,7 @@ class JUnitCategoryAnnotation extends Annotation {
literal = value.(ArrayCreationExpr).getInit().getAnInit()
)
|
result = literal.getTypeName().getType()
result = literal.getReferencedType()
)
}
}

View File

@@ -25,8 +25,8 @@ Expr enumConstEquality(Expr e, boolean polarity, EnumConstant c) {
}
/** Gets an instanceof expression of `v` with type `type` */
InstanceOfExpr instanceofExpr(SsaVariable v, Type type) {
result.getTypeName().getType() = type and
InstanceOfExpr instanceofExpr(SsaVariable v, RefType type) {
result.getCheckedType() = type and
result.getExpr() = v.getAUse()
}

View File

@@ -504,7 +504,7 @@ private predicate correlatedConditions(
inverted = pol1.booleanXor(pol2)
)
or
exists(SsaVariable v, Type type |
exists(SsaVariable v, RefType type |
cond1.getCondition() = instanceofExpr(v, type) and
cond2.getCondition() = instanceofExpr(v, type) and
inverted = false

View File

@@ -299,7 +299,7 @@ private predicate downcastSuccessor(VarAccess va, RefType t) {
private predicate instanceOfGuarded(VarAccess va, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v |
ioe.getExpr() = v.getAUse() and
t = ioe.getTypeName().getType() and
t = ioe.getCheckedType() and
va = v.getAUse() and
guardControls_v1(ioe, va.getBasicBlock(), true)
)
@@ -311,7 +311,7 @@ private predicate instanceOfGuarded(VarAccess va, RefType t) {
predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
ioe.getExpr() = aa1 and
t = ioe.getTypeName().getType() and
t = ioe.getCheckedType() and
aa1.getArray() = v1.getAUse() and
aa1.getIndexExpr() = v2.getAUse() and
aa.getArray() = v1.getAUse() and

View File

@@ -368,7 +368,44 @@ private class ContainerFlowSummaries extends SummaryModelCsv {
"java.util;Collections;false;copy;(List,List);;Element of Argument[1];Element of Argument[0];value",
"java.util;Collections;false;fill;(List,Object);;Argument[1];Element of Argument[0];value",
"java.util;Arrays;false;asList;;;ArrayElement of Argument[0];Element of ReturnValue;value",
"java.util;Collections;false;addAll;(Collection,Object[]);;ArrayElement of Argument[1];Element of Argument[0];value"
"java.util;Collections;false;addAll;(Collection,Object[]);;ArrayElement of Argument[1];Element of Argument[0];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;ArrayDeque;false;ArrayDeque;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;ArrayList;false;ArrayList;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(EnumMap);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;EnumMap;false;EnumMap;(EnumMap);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;HashMap;false;HashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;HashMap;false;HashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;HashSet;false;HashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;Hashtable;false;Hashtable;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;Hashtable;false;Hashtable;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;LinkedHashSet;false;LinkedHashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;LinkedList;false;LinkedList;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(PriorityQueue);;Element of Argument[0];Element of Argument[-1];value",
"java.util;PriorityQueue;false;PriorityQueue;(SortedSet);;Element of Argument[0];Element of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(SortedMap);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;TreeMap;false;TreeMap;(SortedMap);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;TreeSet;false;TreeSet;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;TreeSet;false;TreeSet;(SortedSet);;Element of Argument[0];Element of Argument[-1];value",
"java.util;Vector;false;Vector;(Collection);;Element of Argument[0];Element of Argument[-1];value",
"java.util;WeakHashMap;false;WeakHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;WeakHashMap;false;WeakHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
]
}
}

View File

@@ -176,7 +176,7 @@ private module Dispatch {
v.getAUse() = q and
guardControls_v1(ioe, q.getBasicBlock(), false) and
ioe.getExpr() = v.getAUse() and
ioe.getTypeName().getType().getErasure() = t and
ioe.getCheckedType().getErasure() = t and
tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t
)
}

View File

@@ -64,5 +64,5 @@ class RunWithAnnotation extends Annotation {
/**
* Gets the runner that will be used.
*/
Type getRunner() { result = getValue("value").(TypeLiteral).getTypeName().getType() }
Type getRunner() { result = getValue("value").(TypeLiteral).getReferencedType() }
}

View File

@@ -0,0 +1,174 @@
/**
* Provides classes and predicates for working with the Jackson serialization framework.
*/
import java
private import semmle.code.java.Reflection
private import semmle.code.java.dataflow.DataFlow
private class ObjectMapper extends RefType {
ObjectMapper() {
getASupertype*().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper")
}
}
/** A builder for building Jackson's `JsonMapper`. */
class MapperBuilder extends RefType {
MapperBuilder() {
hasQualifiedName("com.fasterxml.jackson.databind.cfg", "MapperBuilder<JsonMapper,Builder>")
}
}
private class JsonFactory extends RefType {
JsonFactory() { hasQualifiedName("com.fasterxml.jackson.core", "JsonFactory") }
}
private class JsonParser extends RefType {
JsonParser() { hasQualifiedName("com.fasterxml.jackson.core", "JsonParser") }
}
/** A type descriptor in Jackson libraries. For example, `java.lang.Class`. */
class JacksonTypeDescriptorType extends RefType {
JacksonTypeDescriptorType() {
this instanceof TypeClass or
hasQualifiedName("com.fasterxml.jackson.databind", "JavaType") or
hasQualifiedName("com.fasterxml.jackson.core.type", "TypeReference")
}
}
/** A method in `ObjectMapper` that deserialize data. */
class ObjectMapperReadMethod extends Method {
ObjectMapperReadMethod() {
this.getDeclaringType() instanceof ObjectMapper and
this.hasName(["readValue", "readValues", "treeToValue"])
}
}
/** A call that enables the default typing in `ObjectMapper`. */
class EnableJacksonDefaultTyping extends MethodAccess {
EnableJacksonDefaultTyping() {
this.getMethod().getDeclaringType() instanceof ObjectMapper and
this.getMethod().hasName("enableDefaultTyping")
}
}
/** A qualifier of a call to one of the methods in `ObjectMapper` that deserialize data. */
class ObjectMapperReadQualifier extends DataFlow::ExprNode {
ObjectMapperReadQualifier() {
exists(MethodAccess ma | ma.getQualifier() = this.asExpr() |
ma.getMethod() instanceof ObjectMapperReadMethod
)
}
}
/** A source that sets a type validator. */
class SetPolymorphicTypeValidatorSource extends DataFlow::ExprNode {
SetPolymorphicTypeValidatorSource() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
(
m.getDeclaringType() instanceof ObjectMapper and
m.hasName("setPolymorphicTypeValidator")
or
m.getDeclaringType() instanceof MapperBuilder and
m.hasName("polymorphicTypeValidator")
) and
this.asExpr() = ma.getQualifier()
)
}
}
/** Holds if `fromNode` to `toNode` is a dataflow step that resolves a class. */
predicate resolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ReflectiveClassIdentifierMethodAccess ma |
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson parser.
*
* For example, a `createParser(userString)` call yields a `JsonParser`, which becomes dangerous
* if passed to an unsafely-configured `ObjectMapper`'s `readValue` method.
*/
predicate createJacksonJsonParserStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
(m.getDeclaringType() instanceof ObjectMapper or m.getDeclaringType() instanceof JsonFactory) and
m.hasName("createParser") and
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson `TreeNode`.
*
* These are parse trees of user-supplied JSON, which may lead to arbitrary code execution
* if passed to an unsafely-configured `ObjectMapper`'s `treeToValue` method.
*/
predicate createJacksonTreeNodeStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof ObjectMapper and
m.hasName("readTree") and
ma.getArgument(0) = fromNode.asExpr() and
ma = toNode.asExpr()
)
or
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof JsonParser and
m.hasName("readValueAsTree") and
ma.getQualifier() = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* Holds if `type` or one of its supertypes has a field with `JsonTypeInfo` annotation
* that enables polymorphic type handling.
*/
private predicate hasJsonTypeInfoAnnotation(RefType type) {
hasFieldWithJsonTypeAnnotation(type.getASupertype*()) or
hasJsonTypeInfoAnnotation(type.getAField().getType())
}
/**
* Holds if `type` has a field with `JsonTypeInfo` annotation
* that enables polymorphic type handling.
*/
private predicate hasFieldWithJsonTypeAnnotation(RefType type) {
exists(Annotation a |
type.getAField().getAnAnnotation() = a and
a.getType().hasQualifiedName("com.fasterxml.jackson.annotation", "JsonTypeInfo") and
a.getValue("use").(VarAccess).getVariable().hasName(["CLASS", "MINIMAL_CLASS"])
)
}
/**
* Holds if `call` is a method call to a Jackson deserialization method such as `ObjectMapper.readValue(String, Class)`,
* and the target deserialized class has a field with a `JsonTypeInfo` annotation that enables polymorphic typing.
*/
predicate hasArgumentWithUnsafeJacksonAnnotation(MethodAccess call) {
call.getMethod() instanceof ObjectMapperReadMethod and
exists(RefType argType, int i | i > 0 and argType = call.getArgument(i).getType() |
hasJsonTypeInfoAnnotation(argType.(ParameterizedType).getATypeArgument())
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
* A method probably resolves a class if it takes a string, returns a type descriptor,
* and its name contains "resolve", "load", etc.
*
* Any method call that satisfies the rule above is assumed to propagate taint from its string arguments,
* so methods that accept user-controlled data but sanitize it or use it for some
* completely different purpose before returning a type descriptor could result in false positives.
*/
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m, Expr arg | m = ma.getMethod() and arg = ma.getAnArgument() |
m.getReturnType() instanceof JacksonTypeDescriptorType and
m.getName().toLowerCase().regexpMatch("(.*)(resolve|load|class|type)(.*)") and
arg.getType() instanceof TypeString and
arg = fromNode.asExpr() and
ma = toNode.asExpr()
)
}

View File

@@ -33,7 +33,7 @@ class HttpResponseParseAsDeserializableField extends DeserializableField {
HttpResponseParseAsDeserializableField() {
exists(RefType decltype, TypeLiteralToParseAsFlowConfiguration conf |
decltype = getDeclaringType() and
conf.getSourceWithFlowToParseAs().getTypeName().getType() = decltype and
conf.getSourceWithFlowToParseAs().getReferencedType() = decltype and
decltype.fromSource()
)
}

View File

@@ -116,7 +116,7 @@ private class TypeLiteralToJacksonDatabindFlowConfiguration extends DataFlow5::C
private class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType {
ExplicitlyReadJacksonDeserializableType() {
exists(TypeLiteralToJacksonDatabindFlowConfiguration conf |
usesType(conf.getSourceWithFlowToJacksonDatabind().getTypeName().getType(), this)
usesType(conf.getSourceWithFlowToJacksonDatabind().getReferencedType(), this)
)
or
exists(MethodAccess ma |

View File

@@ -199,7 +199,7 @@ abstract class EjbInterfaceAnnotation extends Annotation {
// within the "value" element of this annotation.
// Uses `getAChildExpr*()` since the "value" element can have type `Class` or `Class[]`.
exists(TypeLiteral tl | tl = getValue("value").getAChildExpr*() |
exists(TypeAccess ta | ta = tl.getTypeName() | result = ta.getType())
result = tl.getReferencedType()
)
}
}

View File

@@ -45,7 +45,7 @@ class SpringComponentScan extends Annotation {
// Base package classes are type literals whose package should be considered a base package.
typeLiteral = getAValue("basePackageClasses")
|
result = typeLiteral.getTypeName().getType().(RefType).getPackage().getName()
result = typeLiteral.getReferencedType().(RefType).getPackage().getName()
)
}
}

View File

@@ -1,172 +0,0 @@
import semmle.code.java.frameworks.Kryo
import semmle.code.java.frameworks.XStream
import semmle.code.java.frameworks.SnakeYaml
import semmle.code.java.frameworks.FastJson
import semmle.code.java.frameworks.JYaml
import semmle.code.java.frameworks.JsonIo
import semmle.code.java.frameworks.YamlBeans
import semmle.code.java.frameworks.HessianBurlap
import semmle.code.java.frameworks.Castor
import semmle.code.java.frameworks.apache.Lang
class ObjectInputStreamReadObjectMethod extends Method {
ObjectInputStreamReadObjectMethod() {
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.io", "ObjectInputStream") and
(this.hasName("readObject") or this.hasName("readUnshared"))
}
}
class XMLDecoderReadObjectMethod extends Method {
XMLDecoderReadObjectMethod() {
this.getDeclaringType().hasQualifiedName("java.beans", "XMLDecoder") and
this.hasName("readObject")
}
}
class SafeXStream extends DataFlow2::Configuration {
SafeXStream() { this = "UnsafeDeserialization::SafeXStream" }
override predicate isSource(DataFlow::Node src) {
any(XStreamEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof XStreamReadObjectMethod
)
}
}
class SafeKryo extends DataFlow2::Configuration {
SafeKryo() { this = "UnsafeDeserialization::SafeKryo" }
override predicate isSource(DataFlow::Node src) {
any(KryoEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof KryoReadObjectMethod
)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
stepKryoPoolBuilderFactoryArgToConstructor(node1, node2) or
stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(node1, node2) or
stepKryoPoolBuilderChainMethod(node1, node2) or
stepKryoPoolBorrowMethod(node1, node2)
}
/**
* Holds when a functional expression is used to create a `KryoPool.Builder`.
* Eg. `new KryoPool.Builder(() -> new Kryo())`
*/
private predicate stepKryoPoolBuilderFactoryArgToConstructor(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(ConstructorCall cc, FunctionalExpr fe |
cc.getConstructedType() instanceof KryoPoolBuilder and
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
node2.asExpr() = cc and
cc.getArgument(0) = fe
)
}
/**
* Holds when a `KryoPool.run` is called to use a `Kryo` instance.
* Eg. `pool.run(kryo -> ...)`
*/
private predicate stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolRunMethod and
node1.asExpr() = ma.getQualifier() and
ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter()
)
}
/**
* Holds when a `KryoPool.Builder` method is called fluently.
*/
private predicate stepKryoPoolBuilderChainMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolBuilderMethod and
ma = node2.asExpr() and
ma.getQualifier() = node1.asExpr()
)
}
/**
* Holds when a `KryoPool.borrow` method is called.
*/
private predicate stepKryoPoolBorrowMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() =
any(Method m | m.getDeclaringType() instanceof KryoPool and m.hasName("borrow")) and
node1.asExpr() = ma.getQualifier() and
node2.asExpr() = ma
)
}
}
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof ObjectInputStreamReadObjectMethod and
sink = ma.getQualifier() and
not exists(DataFlow::ExprNode node |
node.getExpr() = sink and
node.getTypeBound()
.(RefType)
.hasQualifiedName("org.apache.commons.io.serialization", "ValidatingObjectInputStream")
)
or
m instanceof XMLDecoderReadObjectMethod and
sink = ma.getQualifier()
or
m instanceof XStreamReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeXStream sxs | sxs.hasFlowToExpr(ma.getQualifier()))
or
m instanceof KryoReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeKryo sk | sk.hasFlowToExpr(ma.getQualifier()))
or
m instanceof MethodApacheSerializationUtilsDeserialize and
sink = ma.getArgument(0)
or
ma instanceof UnsafeSnakeYamlParse and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof FastJsonParseMethod and
not fastJsonLooksSafe() and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JYamlLoaderUnsafeLoadMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoReadObjectMethod and
sink = ma.getQualifier()
or
ma.getMethod() instanceof YamlBeansReaderReadMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof UnsafeHessianInputReadObjectMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof CastorUnmarshalMethod and sink = ma.getAnArgument()
or
ma.getMethod() instanceof BurlapInputReadObjectMethod and sink = ma.getQualifier()
)
}
class UnsafeDeserializationSink extends DataFlow::ExprNode {
UnsafeDeserializationSink() { unsafeDeserialization(_, this.getExpr()) }
MethodAccess getMethodAccess() { unsafeDeserialization(result, this.getExpr()) }
}

View File

@@ -0,0 +1,318 @@
/**
* Provides classes and predicates for finding deserialization vulnerabilities.
*/
import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.TaintTracking2
private import semmle.code.java.frameworks.Kryo
private import semmle.code.java.frameworks.XStream
private import semmle.code.java.frameworks.SnakeYaml
private import semmle.code.java.frameworks.FastJson
private import semmle.code.java.frameworks.JYaml
private import semmle.code.java.frameworks.JsonIo
private import semmle.code.java.frameworks.YamlBeans
private import semmle.code.java.frameworks.HessianBurlap
private import semmle.code.java.frameworks.Castor
private import semmle.code.java.frameworks.Jackson
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.Reflection
private class ObjectInputStreamReadObjectMethod extends Method {
ObjectInputStreamReadObjectMethod() {
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.io", "ObjectInputStream") and
(this.hasName("readObject") or this.hasName("readUnshared"))
}
}
private class XMLDecoderReadObjectMethod extends Method {
XMLDecoderReadObjectMethod() {
this.getDeclaringType().hasQualifiedName("java.beans", "XMLDecoder") and
this.hasName("readObject")
}
}
private class SafeXStream extends DataFlow2::Configuration {
SafeXStream() { this = "UnsafeDeserialization::SafeXStream" }
override predicate isSource(DataFlow::Node src) {
any(XStreamEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof XStreamReadObjectMethod
)
}
}
private class SafeKryo extends DataFlow2::Configuration {
SafeKryo() { this = "UnsafeDeserialization::SafeKryo" }
override predicate isSource(DataFlow::Node src) {
any(KryoEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() =
src.asExpr()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod() instanceof KryoReadObjectMethod
)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
stepKryoPoolBuilderFactoryArgToConstructor(node1, node2) or
stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(node1, node2) or
stepKryoPoolBuilderChainMethod(node1, node2) or
stepKryoPoolBorrowMethod(node1, node2)
}
/**
* Holds when a functional expression is used to create a `KryoPool.Builder`.
* Eg. `new KryoPool.Builder(() -> new Kryo())`
*/
private predicate stepKryoPoolBuilderFactoryArgToConstructor(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(ConstructorCall cc, FunctionalExpr fe |
cc.getConstructedType() instanceof KryoPoolBuilder and
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
node2.asExpr() = cc and
cc.getArgument(0) = fe
)
}
/**
* Holds when a `KryoPool.run` is called to use a `Kryo` instance.
* Eg. `pool.run(kryo -> ...)`
*/
private predicate stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(
DataFlow::Node node1, DataFlow::Node node2
) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolRunMethod and
node1.asExpr() = ma.getQualifier() and
ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter()
)
}
/**
* Holds when a `KryoPool.Builder` method is called fluently.
*/
private predicate stepKryoPoolBuilderChainMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() instanceof KryoPoolBuilderMethod and
ma = node2.asExpr() and
ma.getQualifier() = node1.asExpr()
)
}
/**
* Holds when a `KryoPool.borrow` method is called.
*/
private predicate stepKryoPoolBorrowMethod(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod() =
any(Method m | m.getDeclaringType() instanceof KryoPool and m.hasName("borrow")) and
node1.asExpr() = ma.getQualifier() and
node2.asExpr() = ma
)
}
}
/**
* Holds if `ma` is a call that deserializes data from `sink`.
*/
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof ObjectInputStreamReadObjectMethod and
sink = ma.getQualifier() and
not exists(DataFlow::ExprNode node |
node.getExpr() = sink and
node.getTypeBound()
.(RefType)
.hasQualifiedName("org.apache.commons.io.serialization", "ValidatingObjectInputStream")
)
or
m instanceof XMLDecoderReadObjectMethod and
sink = ma.getQualifier()
or
m instanceof XStreamReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeXStream sxs | sxs.hasFlowToExpr(ma.getQualifier()))
or
m instanceof KryoReadObjectMethod and
sink = ma.getAnArgument() and
not exists(SafeKryo sk | sk.hasFlowToExpr(ma.getQualifier()))
or
m instanceof MethodApacheSerializationUtilsDeserialize and
sink = ma.getArgument(0)
or
ma instanceof UnsafeSnakeYamlParse and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof FastJsonParseMethod and
not fastJsonLooksSafe() and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JYamlLoaderUnsafeLoadMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
sink = ma.getArgument(0)
or
ma.getMethod() instanceof JsonIoReadObjectMethod and
sink = ma.getQualifier()
or
ma.getMethod() instanceof YamlBeansReaderReadMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof UnsafeHessianInputReadObjectMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof CastorUnmarshalMethod and sink = ma.getAnArgument()
or
ma.getMethod() instanceof BurlapInputReadObjectMethod and sink = ma.getQualifier()
or
ma.getMethod() instanceof ObjectMapperReadMethod and
sink = ma.getArgument(0) and
(
exists(UnsafeTypeConfig config | config.hasFlowToExpr(ma.getAnArgument()))
or
exists(EnableJacksonDefaultTypingConfig config | config.hasFlowToExpr(ma.getQualifier()))
or
hasArgumentWithUnsafeJacksonAnnotation(ma)
) and
not exists(SafeObjectMapperConfig config | config.hasFlowToExpr(ma.getQualifier()))
)
}
/** A sink for unsafe deserialization. */
class UnsafeDeserializationSink extends DataFlow::ExprNode {
UnsafeDeserializationSink() { unsafeDeserialization(_, this.getExpr()) }
/** Gets a call that triggers unsafe deserialization. */
MethodAccess getMethodAccess() { unsafeDeserialization(result, this.getExpr()) }
}
/**
* Tracks flows from remote user input to a deserialization sink.
*/
class UnsafeDeserializationConfig extends TaintTracking::Configuration {
UnsafeDeserializationConfig() { this = "UnsafeDeserializationConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassInstanceExpr cie |
cie.getArgument(0) = pred.asExpr() and
cie = succ.asExpr() and
(
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader or
cie.getConstructor().getDeclaringType() instanceof YamlBeansReader or
cie.getConstructor().getDeclaringType().getASupertype*() instanceof UnsafeHessianInput or
cie.getConstructor().getDeclaringType() instanceof BurlapInput
)
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof BurlapInputInitMethod and
ma.getArgument(0) = pred.asExpr() and
ma.getQualifier() = succ.asExpr()
)
or
createJacksonJsonParserStep(pred, succ)
or
createJacksonTreeNodeStep(pred, succ)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ClassInstanceExpr cie |
cie.getConstructor().getDeclaringType() instanceof JsonIoJsonReader and
cie = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(cie.getArgument(1)))
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof JsonIoJsonToJavaMethod and
ma.getArgument(0) = node.asExpr() and
exists(SafeJsonIoConfig sji | sji.hasFlowToExpr(ma.getArgument(1)))
)
}
}
/**
* Tracks flow from a remote source to a type descriptor (e.g. a `java.lang.Class` instance)
* passed to a Jackson deserialization method.
*
* If this is user-controlled, arbitrary code could be executed while instantiating the user-specified type.
*/
class UnsafeTypeConfig extends TaintTracking2::Configuration {
UnsafeTypeConfig() { this = "UnsafeTypeConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, int i, Expr arg | i > 0 and ma.getArgument(i) = arg |
ma.getMethod() instanceof ObjectMapperReadMethod and
arg.getType() instanceof JacksonTypeDescriptorType and
arg = sink.asExpr()
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that resolves a class
* or at least looks like resolving a class.
*/
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
resolveClassStep(fromNode, toNode) or
looksLikeResolveClassStep(fromNode, toNode)
}
}
/**
* Tracks flow from `enableDefaultTyping` calls to a subsequent Jackson deserialization method call.
*/
class EnableJacksonDefaultTypingConfig extends DataFlow2::Configuration {
EnableJacksonDefaultTypingConfig() { this = "EnableJacksonDefaultTypingConfig" }
override predicate isSource(DataFlow::Node src) {
any(EnableJacksonDefaultTyping ma).getQualifier() = src.asExpr()
}
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadQualifier }
}
/**
* Tracks flow from calls that set a type validator to a subsequent Jackson deserialization method call,
* including across builder method calls.
*
* Such a Jackson deserialization method call is safe because validation will likely prevent instantiating unexpected types.
*/
class SafeObjectMapperConfig extends DataFlow2::Configuration {
SafeObjectMapperConfig() { this = "SafeObjectMapperConfig" }
override predicate isSource(DataFlow::Node src) {
src instanceof SetPolymorphicTypeValidatorSource
}
override predicate isSink(DataFlow::Node sink) { sink instanceof ObjectMapperReadQualifier }
/**
* Holds if `fromNode` to `toNode` is a dataflow step
* that configures or creates an `ObjectMapper` via a builder.
*/
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof MapperBuilder and
m.getReturnType()
.(RefType)
.hasQualifiedName("com.fasterxml.jackson.databind.json",
["JsonMapper$Builder", "JsonMapper"]) and
fromNode.asExpr() = ma.getQualifier() and
ma = toNode.asExpr()
)
}
}

View File

@@ -12,8 +12,8 @@ import sys
import tempfile
if any(s == "--help" for s in sys.argv):
print("""Usage:
GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir
print("""Usage:
GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir [--force]
This generates test cases exercising function model specifications found in specsToTest.csv
producing files Test.java, test.ql and test.expected in outdir.
@@ -22,32 +22,41 @@ projectPom.xml should be a Maven pom sufficient to resolve the classes named in
Typically this means supplying a skeleton POM <dependencies> section that retrieves whatever jars
contain the needed classes.
If --force is present, existing files may be overwritten.
Requirements: `mvn` and `codeql` should both appear on your path.
After test generation completes, any lines in specsToTest.csv that didn't produce tests are output.
If this happens, check the spelling of class and method names, and the syntax of input and output specifications.
""")
sys.exit(0)
sys.exit(0)
force = False
if "--force" in sys.argv:
sys.argv.remove("--force")
force = True
if len(sys.argv) != 4:
print("Usage: GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir", file=sys.stderr)
print("specsToTest.csv should contain CSV rows describing method taint-propagation specifications to test", file=sys.stderr)
print("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.csv", file=sys.stderr)
sys.exit(1)
print(
"Usage: GenerateFlowTestCase.py specsToTest.csv projectPom.xml outdir [--force]", file=sys.stderr)
print("specsToTest.csv should contain CSV rows describing method taint-propagation specifications to test", file=sys.stderr)
print("projectPom.xml should import dependencies sufficient to resolve the types used in specsToTest.csv", file=sys.stderr)
sys.exit(1)
try:
os.makedirs(sys.argv[3])
os.makedirs(sys.argv[3])
except Exception as e:
if e.errno != errno.EEXIST:
print("Failed to create output directory %s: %s" % (sys.argv[3], e))
sys.exit(1)
if e.errno != errno.EEXIST:
print("Failed to create output directory %s: %s" % (sys.argv[3], e))
sys.exit(1)
resultJava = os.path.join(sys.argv[3], "Test.java")
resultQl = os.path.join(sys.argv[3], "test.ql")
if os.path.exists(resultJava) or os.path.exists(resultQl):
print("Won't overwrite existing files '%s' or '%s'" % (resultJava, resultQl), file = sys.stderr)
sys.exit(1)
if not force and (os.path.exists(resultJava) or os.path.exists(resultQl)):
print("Won't overwrite existing files '%s' or '%s'" %
(resultJava, resultQl), file=sys.stderr)
sys.exit(1)
workDir = tempfile.mkdtemp()
@@ -57,129 +66,159 @@ projectDir = os.path.join(workDir, "mavenProject")
os.makedirs(projectDir)
try:
shutil.copyfile(sys.argv[2], os.path.join(projectDir, "pom.xml"))
shutil.copyfile(sys.argv[2], os.path.join(projectDir, "pom.xml"))
except Exception as e:
print("Failed to read project POM %s: %s" % (sys.argv[2], e), file = sys.stderr)
sys.exit(1)
print("Failed to read project POM %s: %s" %
(sys.argv[2], e), file=sys.stderr)
sys.exit(1)
commentRegex = re.compile("^\s*(//|#)")
def isComment(s):
return commentRegex.match(s) is not None
return commentRegex.match(s) is not None
try:
with open(sys.argv[1], "r") as f:
specs = [l for l in f if not isComment(l)]
with open(sys.argv[1], "r") as f:
specs = [l for l in f if not isComment(l)]
except Exception as e:
print("Failed to open %s: %s\n" % (sys.argv[1], e))
sys.exit(1)
print("Failed to open %s: %s\n" % (sys.argv[1], e))
sys.exit(1)
projectTestPkgDir = os.path.join(projectDir, "src", "main", "java", "test")
projectTestFile = os.path.join(projectTestPkgDir, "Test.java")
os.makedirs(projectTestPkgDir)
def qualifiedOuterNameFromCsvRow(row):
cells = row.split(";")
if len(cells) < 2:
return None
return cells[0] + "." + cells[1].replace("$", ".")
cells = row.split(";")
if len(cells) < 2:
return None
return cells[0] + "." + cells[1].replace("$", ".")
with open(projectTestFile, "w") as testJava:
testJava.write("package test;\n\npublic class Test {\n\n")
testJava.write("package test;\n\npublic class Test {\n\n")
for i, spec in enumerate(specs):
outerName = qualifiedOuterNameFromCsvRow(spec)
if outerName is None:
print("A taint specification has the wrong format: should be 'package;classname;methodname....'", file = sys.stderr)
print("Mis-formatted row: " + spec, file = sys.stderr)
sys.exit(1)
testJava.write("\t%s obj%d = null;\n" % (outerName, i))
for i, spec in enumerate(specs):
outerName = qualifiedOuterNameFromCsvRow(spec)
if outerName is None:
print("A taint specification has the wrong format: should be 'package;classname;methodname....'", file=sys.stderr)
print("Mis-formatted row: " + spec, file=sys.stderr)
sys.exit(1)
testJava.write("\t%s obj%d = null;\n" % (outerName, i))
testJava.write("}")
testJava.write("}")
print("Creating project database")
cmd = ["codeql", "database", "create", "--language=java", "db"]
ret = subprocess.call(cmd, cwd = projectDir)
ret = subprocess.call(cmd, cwd=projectDir)
if ret != 0:
print("Failed to create project database. Check that '%s' is a valid POM that pulls in all necessary dependencies, and '%s' specifies valid classes and methods." % (sys.argv[2], sys.argv[1]), file = sys.stderr)
print("Failed command was: %s (cwd: %s)" % (shlex.join(cmd), projectDir), file = sys.stderr)
sys.exit(1)
print("Failed to create project database. Check that '%s' is a valid POM that pulls in all necessary dependencies, and '%s' specifies valid classes and methods." % (
sys.argv[2], sys.argv[1]), file=sys.stderr)
print("Failed command was: %s (cwd: %s)" %
(shlex.join(cmd), projectDir), file=sys.stderr)
sys.exit(1)
print("Creating test-generation query")
queryDir = os.path.join(workDir, "query")
os.makedirs(queryDir)
qlFile = os.path.join(queryDir, "gen.ql")
with open(os.path.join(queryDir, "qlpack.yml"), "w") as f:
f.write("name: test-generation-query\nversion: 0.0.0\nlibraryPathDependencies: codeql-java")
f.write("name: test-generation-query\nversion: 0.0.0\nlibraryPathDependencies: codeql-java")
with open(qlFile, "w") as f:
f.write("import java\nimport utils.GenerateFlowTestCase\n\nclass GenRow extends TargetSummaryModelCsv {\n\n\toverride predicate row(string r) {\n\t\tr = [\n")
f.write(",\n".join('\t\t\t"%s"' % spec.strip() for spec in specs))
f.write("\n\t\t]\n\t}\n}\n")
f.write(
"import java\nimport utils.GenerateFlowTestCase\n\nclass GenRow extends TargetSummaryModelCsv {\n\n\toverride predicate row(string r) {\n\t\tr = [\n")
f.write(",\n".join('\t\t\t"%s"' % spec.strip() for spec in specs))
f.write("\n\t\t]\n\t}\n}\n")
print("Generating tests")
generatedBqrs = os.path.join(queryDir, "out.bqrs")
cmd = ['codeql', 'query', 'run', qlFile, '--database', os.path.join(projectDir, "db"), '--output', generatedBqrs]
cmd = ['codeql', 'query', 'run', qlFile, '--database',
os.path.join(projectDir, "db"), '--output', generatedBqrs]
ret = subprocess.call(cmd)
if ret != 0:
print("Failed to generate tests. Failed command was: " + shlex.join(cmd))
sys.exit(1)
print("Failed to generate tests. Failed command was: " + shlex.join(cmd))
sys.exit(1)
generatedJson = os.path.join(queryDir, "out.json")
cmd = ['codeql', 'bqrs', 'decode', generatedBqrs, '--format=json', '--output', generatedJson]
cmd = ['codeql', 'bqrs', 'decode', generatedBqrs,
'--format=json', '--output', generatedJson]
ret = subprocess.call(cmd)
if ret != 0:
print("Failed to decode BQRS. Failed command was: " + shlex.join(cmd))
sys.exit(1)
print("Failed to decode BQRS. Failed command was: " + shlex.join(cmd))
sys.exit(1)
def getTuples(queryName, jsonResult, fname):
if queryName not in jsonResult or "tuples" not in jsonResult[queryName]:
print("Failed to read generated tests: expected key '%s' with a 'tuples' subkey in file '%s'" % (queryName, fname), file = sys.stderr)
sys.exit(1)
return jsonResult[queryName]["tuples"]
if queryName not in jsonResult or "tuples" not in jsonResult[queryName]:
print("Failed to read generated tests: expected key '%s' with a 'tuples' subkey in file '%s'" % (
queryName, fname), file=sys.stderr)
sys.exit(1)
return jsonResult[queryName]["tuples"]
with open(generatedJson, "r") as f:
generateOutput = json.load(f)
expectedTables = ("getTestCase", "getASupportMethodModel", "missingSummaryModelCsv", "getAParseFailure")
generateOutput = json.load(f)
expectedTables = ("getTestCase", "getASupportMethodModel",
"missingSummaryModelCsv", "getAParseFailure", "noTestCaseGenerated")
testCaseRows, supportModelRows, missingSummaryModelCsvRows, parseFailureRows = \
tuple([getTuples(k, generateOutput, generatedJson) for k in expectedTables])
testCaseRows, supportModelRows, missingSummaryModelCsvRows, parseFailureRows, noTestCaseGeneratedRows = \
tuple([getTuples(k, generateOutput, generatedJson)
for k in expectedTables])
if len(testCaseRows) != 1 or len(testCaseRows[0]) != 1:
print("Expected exactly one getTestCase result with one column (got: %s)" % json.dumps(testCaseRows), file = sys.stderr)
if any(len(row) != 1 for row in supportModelRows):
print("Expected exactly one column in getASupportMethodModel relation (got: %s)" % json.dumps(supportModelRows), file = sys.stderr)
if any(len(row) != 2 for row in parseFailureRows):
print("Expected exactly two columns in parseFailureRows relation (got: %s)" % json.dumps(parseFailureRows), file = sys.stderr)
if len(testCaseRows) != 1 or len(testCaseRows[0]) != 1:
print("Expected exactly one getTestCase result with one column (got: %s)" %
json.dumps(testCaseRows), file=sys.stderr)
if any(len(row) != 1 for row in supportModelRows):
print("Expected exactly one column in getASupportMethodModel relation (got: %s)" %
json.dumps(supportModelRows), file=sys.stderr)
if any(len(row) != 2 for row in parseFailureRows):
print("Expected exactly two columns in parseFailureRows relation (got: %s)" %
json.dumps(parseFailureRows), file=sys.stderr)
if any(len(row) != 1 for row in noTestCaseGeneratedRows):
print("Expected exactly one column in noTestCaseGenerated relation (got: %s)" %
json.dumps(noTestCaseGeneratedRows), file=sys.stderr)
if len(missingSummaryModelCsvRows) != 0:
print("Tests for some CSV rows were requested that were not in scope (SummaryModelCsv.row does not hold):\n" + "\n".join(r[0] for r in missingSummaryModelCsvRows))
sys.exit(1)
if len(parseFailureRows) != 0:
print("The following rows failed to generate any test case. Check package, class and method name spelling, and argument and result specifications:\n%s" % "\n".join(r[0] + ": " + r[1] for r in parseFailureRows), file = sys.stderr)
sys.exit(1)
if len(missingSummaryModelCsvRows) != 0:
print("Tests for some CSV rows were requested that were not in scope (SummaryModelCsv.row does not hold):\n" +
"\n".join(r[0] for r in missingSummaryModelCsvRows))
sys.exit(1)
if len(parseFailureRows) != 0:
print("The following rows failed to generate any test case. Check package, class and method name spelling, and argument and result specifications:\n%s" %
"\n".join(r[0] + ": " + r[1] for r in parseFailureRows), file=sys.stderr)
sys.exit(1)
if len(noTestCaseGeneratedRows) != 0:
print("The following CSV rows failed to generate any test case due to a limitation of the query. Other test cases will still be generated:\n" +
"\n".join(r[0] for r in noTestCaseGeneratedRows))
with open(resultJava, "w") as f:
f.write(generateOutput["getTestCase"]["tuples"][0][0])
f.write(generateOutput["getTestCase"]["tuples"][0][0])
scriptPath = os.path.dirname(sys.argv[0])
def copyfile(fromName, toFileHandle):
with open(os.path.join(scriptPath, fromName), "r") as fromFileHandle:
shutil.copyfileobj(fromFileHandle, toFileHandle)
with open(os.path.join(scriptPath, fromName), "r") as fromFileHandle:
shutil.copyfileobj(fromFileHandle, toFileHandle)
with open(resultQl, "w") as f:
copyfile("testHeader.qlfrag", f)
if len(supportModelRows) != 0:
copyfile("testModelsHeader.qlfrag", f)
f.write(", ".join('"%s"' % modelSpecRow[0].strip() for modelSpecRow in supportModelRows))
copyfile("testModelsFooter.qlfrag", f)
copyfile("testFooter.qlfrag", f)
copyfile("testHeader.qlfrag", f)
if len(supportModelRows) != 0:
copyfile("testModelsHeader.qlfrag", f)
f.write(", ".join('"%s"' %
modelSpecRow[0].strip() for modelSpecRow in supportModelRows))
copyfile("testModelsFooter.qlfrag", f)
copyfile("testFooter.qlfrag", f)
# Make an empty .expected file, since this is an inline-exectations test
with open(os.path.join(sys.argv[3], "test.expected"), "w"):
pass
pass
cmd = ['codeql', 'query', 'format', '-qq', '-i', resultQl]
subprocess.call(cmd)
shutil.rmtree(workDir)
shutil.rmtree(workDir)

View File

@@ -37,6 +37,9 @@ query string getAParseFailure(string reason) {
any(TargetSummaryModelCsv target).row(result) and
any(SummaryModelCsv model).row(result) and
(
not summaryModel(_, _, _, _, _, _, _, _, _, result) and
reason = "row could not be parsed"
or
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
@@ -59,13 +62,26 @@ query string getAParseFailure(string reason) {
)
}
/**
* Gets a CSV row for which a test was requested and was correctly parsed,
* but for which no test case could be generated due to a limitation of the query.
*/
query string noTestCaseGenerated() {
any(TargetSummaryModelCsv target).row(result) and
any(SummaryModelCsv model).row(result) and
not exists(getAParseFailure(_)) and
not exists(any(TestCase tc).getATestSnippetForRow(result))
}
private class CallableToTest extends Callable {
CallableToTest() {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _) and
this = interpretElement(namespace, type, subtypes, name, signature, ext)
this = interpretElement(namespace, type, subtypes, name, signature, ext) and
this.isPublic() and
getRootType(this.getDeclaringType()).isPublic()
)
}
}
@@ -124,6 +140,21 @@ string getFieldToken(FieldContent fc) {
fc.getField().getName()
}
/**
* Returns a valid Java token naming the synthetic field `fc`,
* assuming that the name of that field consists only of characters valid in a Java identifier and `.`.
*/
string getSyntheticFieldToken(SyntheticFieldContent fc) {
exists(string name, int parts |
name = fc.getField() and
parts = count(name.splitAt("."))
|
if parts = 1
then result = name
else result = name.splitAt(".", parts - 2) + "_" + name.splitAt(".", parts - 1)
)
}
/**
* Returns a token suitable for incorporation into a Java method name describing content `c`.
*/
@@ -137,6 +168,8 @@ string contentToken(Content c) {
c instanceof MapValueContent and result = "MapValue"
or
result = getFieldToken(c)
or
result = getSyntheticFieldToken(c)
}
/**
@@ -145,7 +178,10 @@ string contentToken(Content c) {
RefType getRootType(RefType t) {
if t instanceof NestedType
then result = getRootType(t.(NestedType).getEnclosingType())
else result = t
else
if t instanceof Array
then result = getRootType(t.(Array).getElementType())
else result = t
}
/**
@@ -406,6 +442,8 @@ class TestCase extends TTestCase {
content instanceof CollectionContent and result = "Element"
or
result = "Field[" + content.(FieldContent).getField().getQualifiedName() + "]"
or
result = "SyntheticField[" + content.(SyntheticFieldContent).getField() + "]"
)
)
}
@@ -482,18 +520,22 @@ predicate isImportable(Type t) {
* if we cannot import it due to a name clash.
*/
string getShortNameIfPossible(Type t) {
getRootSourceDeclaration(t) = any(TestCase tc).getADesiredImport() and
if t instanceof RefType
then
exists(RefType replaced, string nestedName |
replaced = replaceTypeVariable(t).getSourceDeclaration() and
nestedName = replaced.nestedName().replaceAll("$", ".")
|
if isImportable(getRootSourceDeclaration(t))
then result = nestedName
else result = replaced.getPackage().getName() + "." + nestedName
)
else result = t.getName()
if t instanceof Array
then result = getShortNameIfPossible(t.(Array).getComponentType()) + "[]"
else (
getRootSourceDeclaration(t) = any(TestCase tc).getADesiredImport() and
if t instanceof RefType
then
exists(RefType replaced, string nestedName |
replaced = replaceTypeVariable(t).getSourceDeclaration() and
nestedName = replaced.nestedName().replaceAll("$", ".")
|
if isImportable(getRootSourceDeclaration(t))
then result = nestedName
else result = replaced.getPackage().getName() + "." + nestedName
)
else result = t.getName()
)
}
/**
@@ -530,7 +572,7 @@ query string getTestCase() {
result =
"package generatedtest;\n\n" + concat(getAnImportStatement() + "\n") +
"\n// Test case generated by GenerateFlowTestCase.ql\npublic class Test {\n\n" +
concat("\t" + getASupportMethod() + "\n") + "\n\tpublic void test() {\n\n" +
concat("\t" + getASupportMethod() + "\n") + "\n\tpublic void test() throws Exception {\n\n" +
concat(string row, string snippet |
snippet = any(TestCase tc).getATestSnippetForRow(row)
|

View File

@@ -1,35 +1,90 @@
edges
| SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | SpringUrlRedirect.java:15:19:15:29 | redirectUrl |
| SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | SpringUrlRedirect.java:21:36:21:46 | redirectUrl |
| SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | SpringUrlRedirect.java:27:44:27:54 | redirectUrl |
| SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | SpringUrlRedirect.java:33:47:33:57 | redirectUrl |
| SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | SpringUrlRedirect.java:40:29:40:39 | redirectUrl |
| SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | SpringUrlRedirect.java:48:30:48:40 | redirectUrl |
| SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | SpringUrlRedirect.java:54:30:54:66 | format(...) |
| SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | SpringUrlRedirect.java:59:30:59:76 | format(...) |
| SpringUrlRedirect.java:17:30:17:47 | redirectUrl : String | SpringUrlRedirect.java:19:19:19:29 | redirectUrl |
| SpringUrlRedirect.java:24:24:24:41 | redirectUrl : String | SpringUrlRedirect.java:25:36:25:46 | redirectUrl |
| SpringUrlRedirect.java:30:30:30:47 | redirectUrl : String | SpringUrlRedirect.java:31:44:31:54 | redirectUrl |
| SpringUrlRedirect.java:36:30:36:47 | redirectUrl : String | SpringUrlRedirect.java:37:47:37:57 | redirectUrl |
| SpringUrlRedirect.java:41:24:41:41 | redirectUrl : String | SpringUrlRedirect.java:44:29:44:39 | redirectUrl |
| SpringUrlRedirect.java:49:24:49:41 | redirectUrl : String | SpringUrlRedirect.java:52:30:52:40 | redirectUrl |
| SpringUrlRedirect.java:57:24:57:41 | redirectUrl : String | SpringUrlRedirect.java:58:30:58:66 | format(...) |
| SpringUrlRedirect.java:62:24:62:41 | redirectUrl : String | SpringUrlRedirect.java:63:30:63:76 | format(...) |
| SpringUrlRedirect.java:89:38:89:55 | redirectUrl : String | SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String |
| SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | SpringUrlRedirect.java:91:27:91:49 | create(...) |
| SpringUrlRedirect.java:96:39:96:56 | redirectUrl : String | SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String |
| SpringUrlRedirect.java:98:33:98:55 | create(...) : URI | SpringUrlRedirect.java:100:37:100:47 | httpHeaders |
| SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String | SpringUrlRedirect.java:98:33:98:55 | create(...) : URI |
| SpringUrlRedirect.java:104:39:104:56 | redirectUrl : String | SpringUrlRedirect.java:106:37:106:47 | redirectUrl : String |
| SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] : HttpHeaders | SpringUrlRedirect.java:108:68:108:78 | httpHeaders |
| SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] [<map.value>, <element>] : String | SpringUrlRedirect.java:108:68:108:78 | httpHeaders |
| SpringUrlRedirect.java:106:37:106:47 | redirectUrl : String | SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] : HttpHeaders |
| SpringUrlRedirect.java:106:37:106:47 | redirectUrl : String | SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] [<map.value>, <element>] : String |
| SpringUrlRedirect.java:112:39:112:56 | redirectUrl : String | SpringUrlRedirect.java:114:37:114:47 | redirectUrl : String |
| SpringUrlRedirect.java:114:9:114:19 | httpHeaders [post update] : HttpHeaders | SpringUrlRedirect.java:116:37:116:47 | httpHeaders |
| SpringUrlRedirect.java:114:9:114:19 | httpHeaders [post update] [<map.value>, <element>] : String | SpringUrlRedirect.java:116:37:116:47 | httpHeaders |
| SpringUrlRedirect.java:114:37:114:47 | redirectUrl : String | SpringUrlRedirect.java:114:9:114:19 | httpHeaders [post update] : HttpHeaders |
| SpringUrlRedirect.java:114:37:114:47 | redirectUrl : String | SpringUrlRedirect.java:114:9:114:19 | httpHeaders [post update] [<map.value>, <element>] : String |
| SpringUrlRedirect.java:120:33:120:50 | redirectUrl : String | SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String |
| SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] : HttpHeaders | SpringUrlRedirect.java:124:49:124:59 | httpHeaders |
| SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] [<map.value>, <element>] : String | SpringUrlRedirect.java:124:49:124:59 | httpHeaders |
| SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String | SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] : HttpHeaders |
| SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String | SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] [<map.value>, <element>] : String |
| SpringUrlRedirect.java:128:33:128:50 | redirectUrl : String | SpringUrlRedirect.java:130:44:130:54 | redirectUrl : String |
| SpringUrlRedirect.java:130:33:130:55 | create(...) : URI | SpringUrlRedirect.java:132:49:132:59 | httpHeaders |
| SpringUrlRedirect.java:130:44:130:54 | redirectUrl : String | SpringUrlRedirect.java:130:33:130:55 | create(...) : URI |
nodes
| SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:15:19:15:29 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:21:36:21:46 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:27:44:27:54 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:33:47:33:57 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:40:29:40:39 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:48:30:48:40 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:54:30:54:66 | format(...) | semmle.label | format(...) |
| SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:59:30:59:76 | format(...) | semmle.label | format(...) |
| SpringUrlRedirect.java:17:30:17:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:19:19:19:29 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:24:24:24:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:25:36:25:46 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:30:30:30:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:31:44:31:54 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:36:30:36:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:37:47:37:57 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:41:24:41:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:44:29:44:39 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:49:24:49:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:52:30:52:40 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:57:24:57:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:58:30:58:66 | format(...) | semmle.label | format(...) |
| SpringUrlRedirect.java:62:24:62:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:63:30:63:76 | format(...) | semmle.label | format(...) |
| SpringUrlRedirect.java:89:38:89:55 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:91:27:91:49 | create(...) | semmle.label | create(...) |
| SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:96:39:96:56 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:98:33:98:55 | create(...) : URI | semmle.label | create(...) : URI |
| SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:100:37:100:47 | httpHeaders | semmle.label | httpHeaders |
| SpringUrlRedirect.java:104:39:104:56 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] : HttpHeaders | semmle.label | httpHeaders [post update] : HttpHeaders |
| SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] [<map.value>, <element>] : String | semmle.label | httpHeaders [post update] [<map.value>, <element>] : String |
| SpringUrlRedirect.java:106:37:106:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:108:68:108:78 | httpHeaders | semmle.label | httpHeaders |
| SpringUrlRedirect.java:112:39:112:56 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:114:9:114:19 | httpHeaders [post update] : HttpHeaders | semmle.label | httpHeaders [post update] : HttpHeaders |
| SpringUrlRedirect.java:114:9:114:19 | httpHeaders [post update] [<map.value>, <element>] : String | semmle.label | httpHeaders [post update] [<map.value>, <element>] : String |
| SpringUrlRedirect.java:114:37:114:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:116:37:116:47 | httpHeaders | semmle.label | httpHeaders |
| SpringUrlRedirect.java:120:33:120:50 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] : HttpHeaders | semmle.label | httpHeaders [post update] : HttpHeaders |
| SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] [<map.value>, <element>] : String | semmle.label | httpHeaders [post update] [<map.value>, <element>] : String |
| SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:124:49:124:59 | httpHeaders | semmle.label | httpHeaders |
| SpringUrlRedirect.java:128:33:128:50 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:130:33:130:55 | create(...) : URI | semmle.label | create(...) : URI |
| SpringUrlRedirect.java:130:44:130:54 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:132:49:132:59 | httpHeaders | semmle.label | httpHeaders |
#select
| SpringUrlRedirect.java:15:19:15:29 | redirectUrl | SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | SpringUrlRedirect.java:15:19:15:29 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:13:30:13:47 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:21:36:21:46 | redirectUrl | SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | SpringUrlRedirect.java:21:36:21:46 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:20:24:20:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:27:44:27:54 | redirectUrl | SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | SpringUrlRedirect.java:27:44:27:54 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:26:30:26:47 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:33:47:33:57 | redirectUrl | SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | SpringUrlRedirect.java:33:47:33:57 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:32:30:32:47 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:40:29:40:39 | redirectUrl | SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | SpringUrlRedirect.java:40:29:40:39 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:37:24:37:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:48:30:48:40 | redirectUrl | SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | SpringUrlRedirect.java:48:30:48:40 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:45:24:45:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:54:30:54:66 | format(...) | SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | SpringUrlRedirect.java:54:30:54:66 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:53:24:53:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:59:30:59:76 | format(...) | SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | SpringUrlRedirect.java:59:30:59:76 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:58:24:58:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:19:19:19:29 | redirectUrl | SpringUrlRedirect.java:17:30:17:47 | redirectUrl : String | SpringUrlRedirect.java:19:19:19:29 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:17:30:17:47 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:25:36:25:46 | redirectUrl | SpringUrlRedirect.java:24:24:24:41 | redirectUrl : String | SpringUrlRedirect.java:25:36:25:46 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:24:24:24:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:31:44:31:54 | redirectUrl | SpringUrlRedirect.java:30:30:30:47 | redirectUrl : String | SpringUrlRedirect.java:31:44:31:54 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:30:30:30:47 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:37:47:37:57 | redirectUrl | SpringUrlRedirect.java:36:30:36:47 | redirectUrl : String | SpringUrlRedirect.java:37:47:37:57 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:36:30:36:47 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:44:29:44:39 | redirectUrl | SpringUrlRedirect.java:41:24:41:41 | redirectUrl : String | SpringUrlRedirect.java:44:29:44:39 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:41:24:41:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:52:30:52:40 | redirectUrl | SpringUrlRedirect.java:49:24:49:41 | redirectUrl : String | SpringUrlRedirect.java:52:30:52:40 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:49:24:49:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:58:30:58:66 | format(...) | SpringUrlRedirect.java:57:24:57:41 | redirectUrl : String | SpringUrlRedirect.java:58:30:58:66 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:57:24:57:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:63:30:63:76 | format(...) | SpringUrlRedirect.java:62:24:62:41 | redirectUrl : String | SpringUrlRedirect.java:63:30:63:76 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:62:24:62:41 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:91:27:91:49 | create(...) | SpringUrlRedirect.java:89:38:89:55 | redirectUrl : String | SpringUrlRedirect.java:91:27:91:49 | create(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:89:38:89:55 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:100:37:100:47 | httpHeaders | SpringUrlRedirect.java:96:39:96:56 | redirectUrl : String | SpringUrlRedirect.java:100:37:100:47 | httpHeaders | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:96:39:96:56 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:108:68:108:78 | httpHeaders | SpringUrlRedirect.java:104:39:104:56 | redirectUrl : String | SpringUrlRedirect.java:108:68:108:78 | httpHeaders | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:104:39:104:56 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:116:37:116:47 | httpHeaders | SpringUrlRedirect.java:112:39:112:56 | redirectUrl : String | SpringUrlRedirect.java:116:37:116:47 | httpHeaders | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:112:39:112:56 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:124:49:124:59 | httpHeaders | SpringUrlRedirect.java:120:33:120:50 | redirectUrl : String | SpringUrlRedirect.java:124:49:124:59 | httpHeaders | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:120:33:120:50 | redirectUrl | user-provided value |
| SpringUrlRedirect.java:132:49:132:59 | httpHeaders | SpringUrlRedirect.java:128:33:128:50 | redirectUrl : String | SpringUrlRedirect.java:132:49:132:59 | httpHeaders | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:128:33:128:50 | redirectUrl | user-provided value |

View File

@@ -3,6 +3,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import java.net.URI;
@Controller
public class SpringUrlRedirect {
@@ -80,4 +84,51 @@ public class SpringUrlRedirect {
public String good3(String status) {
return "redirect:" + String.format("/stories/search/criteria?status=%s", status);
}
@GetMapping("url12")
public ResponseEntity<Void> bad9(String redirectUrl) {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(redirectUrl))
.build();
}
@GetMapping("url13")
public ResponseEntity<Void> bad10(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(URI.create(redirectUrl));
return new ResponseEntity<>(httpHeaders, HttpStatus.SEE_OTHER);
}
@GetMapping("url14")
public ResponseEntity<Void> bad11(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Location", redirectUrl);
return ResponseEntity.status(HttpStatus.SEE_OTHER).headers(httpHeaders).build();
}
@GetMapping("url15")
public ResponseEntity<Void> bad12(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Location", redirectUrl);
return new ResponseEntity<>(httpHeaders, HttpStatus.SEE_OTHER);
}
@GetMapping("url16")
public ResponseEntity bad13(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Location", redirectUrl);
return new ResponseEntity<>("TestBody", httpHeaders, HttpStatus.SEE_OTHER);
}
@GetMapping("url17")
public ResponseEntity bad14(String redirectUrl) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(URI.create(redirectUrl));
return new ResponseEntity<>("TestBody", httpHeaders, HttpStatus.SEE_OTHER);
}
}

View File

@@ -1,5 +1,5 @@
import default
import semmle.code.java.security.UnsafeDeserialization
import semmle.code.java.security.UnsafeDeserializationQuery
from Method m, MethodAccess ma
where ma.getMethod() = m and unsafeDeserialization(ma, _)

View File

@@ -0,0 +1,286 @@
package generatedtest;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.WeakHashMap;
// Test case generated by GenerateFlowTestCase.ql
public class Constructors {
Object getElement(Collection container) { return container.iterator().next(); }
Object getMapKey(Map container) { return container.keySet().iterator().next(); }
Object getMapValue(Map container) { return container.get(null); }
Object getMapKey(Map.Entry container) { return container.getKey(); }
Object getMapValue(Map.Entry container) { return container.getValue(); }
Object source() { return null; }
void sink(Object o) { }
public void test() {
{
// "java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value"
AbstractMap.SimpleEntry out = null;
Map.Entry in = new AbstractMap.SimpleEntry(source(), null);
out = new AbstractMap.SimpleEntry(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value"
AbstractMap.SimpleEntry out = null;
Map.Entry in = new AbstractMap.SimpleEntry(null, source());;
out = new AbstractMap.SimpleEntry(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[0];MapKey of Argument[-1];value"
AbstractMap.SimpleEntry out = null;
Object in = source();
out = new AbstractMap.SimpleEntry(in, null);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;AbstractMap$SimpleEntry;false;SimpleEntry;(Object,Object);;Argument[1];MapValue of Argument[-1];value"
AbstractMap.SimpleEntry out = null;
Object in = source();
out = new AbstractMap.SimpleEntry(null, in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapKey of Argument[0];MapKey of Argument[-1];value"
AbstractMap.SimpleImmutableEntry out = null;
Map.Entry in = new AbstractMap.SimpleEntry(source(), null);
out = new AbstractMap.SimpleImmutableEntry(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;AbstractMap$SimpleImmutableEntry;false;SimpleImmutableEntry;(Entry);;MapValue of Argument[0];MapValue of Argument[-1];value"
AbstractMap.SimpleImmutableEntry out = null;
Map.Entry in = new AbstractMap.SimpleEntry(null, source());
out = new AbstractMap.SimpleImmutableEntry(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;ArrayDeque;false;ArrayDeque;(Collection);;Element of Argument[0];Element of Argument[-1];value"
ArrayDeque out = null;
Collection in = List.of(source());
out = new ArrayDeque(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;ArrayList;false;ArrayList;(Collection);;Element of Argument[0];Element of Argument[-1];value"
ArrayList out = null;
Collection in = List.of(source());
out = new ArrayList(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;EnumMap;false;EnumMap;(EnumMap);;MapKey of Argument[0];MapKey of Argument[-1];value"
EnumMap out = null;
EnumMap in = new EnumMap(Map.of(source(), null));
out = new EnumMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;EnumMap;false;EnumMap;(EnumMap);;MapValue of Argument[0];MapValue of Argument[-1];value"
EnumMap out = null;
EnumMap in = new EnumMap(Map.of(null, source()));
out = new EnumMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;EnumMap;false;EnumMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
EnumMap out = null;
Map in = Map.of(source(), null);
out = new EnumMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;EnumMap;false;EnumMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
EnumMap out = null;
Map in = Map.of(null, source());
out = new EnumMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;HashMap;false;HashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
HashMap out = null;
Map in = Map.of(source(), null);
out = new HashMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;HashMap;false;HashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
HashMap out = null;
Map in = Map.of(null, source());
out = new HashMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;HashSet;false;HashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value"
HashSet out = null;
Collection in = List.of(source());
out = new HashSet(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;Hashtable;false;Hashtable;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
Hashtable out = null;
Map in = Map.of(source(), null);
out = new Hashtable(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;Hashtable;false;Hashtable;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
Hashtable out = null;
Map in = Map.of(null, source());
out = new Hashtable(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
IdentityHashMap out = null;
Map in = Map.of(source(), null);
out = new IdentityHashMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;IdentityHashMap;false;IdentityHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
IdentityHashMap out = null;
Map in = Map.of(null, source());
out = new IdentityHashMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
LinkedHashMap out = null;
Map in = Map.of(source(), null);
out = new LinkedHashMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;LinkedHashMap;false;LinkedHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
LinkedHashMap out = null;
Map in = Map.of(null, source());
out = new LinkedHashMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;LinkedHashSet;false;LinkedHashSet;(Collection);;Element of Argument[0];Element of Argument[-1];value"
LinkedHashSet out = null;
Collection in = List.of(source());
out = new LinkedHashSet(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;LinkedList;false;LinkedList;(Collection);;Element of Argument[0];Element of Argument[-1];value"
LinkedList out = null;
Collection in = List.of(source());
out = new LinkedList(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;PriorityQueue;false;PriorityQueue;(Collection);;Element of Argument[0];Element of Argument[-1];value"
PriorityQueue out = null;
Collection in = List.of(source());
out = new PriorityQueue(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;PriorityQueue;false;PriorityQueue;(PriorityQueue);;Element of Argument[0];Element of Argument[-1];value"
PriorityQueue out = null;
PriorityQueue in = new PriorityQueue(List.of(source()));
out = new PriorityQueue(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;PriorityQueue;false;PriorityQueue;(SortedSet);;Element of Argument[0];Element of Argument[-1];value"
PriorityQueue out = null;
SortedSet in = new TreeSet(List.of(source()));
out = new PriorityQueue(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;TreeMap;false;TreeMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
TreeMap out = null;
Map in = Map.of(source(), null);
out = new TreeMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;TreeMap;false;TreeMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
TreeMap out = null;
Map in = Map.of(null, source());
out = new TreeMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;TreeMap;false;TreeMap;(SortedMap);;MapKey of Argument[0];MapKey of Argument[-1];value"
TreeMap out = null;
SortedMap in = new TreeMap(Map.of(source(), null));
out = new TreeMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;TreeMap;false;TreeMap;(SortedMap);;MapValue of Argument[0];MapValue of Argument[-1];value"
TreeMap out = null;
SortedMap in = new TreeMap(Map.of(null, source()));
out = new TreeMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
{
// "java.util;TreeSet;false;TreeSet;(Collection);;Element of Argument[0];Element of Argument[-1];value"
TreeSet out = null;
Collection in = List.of(source());
out = new TreeSet(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;TreeSet;false;TreeSet;(SortedSet);;Element of Argument[0];Element of Argument[-1];value"
TreeSet out = null;
SortedSet in = new TreeSet(List.of(source()));
out = new TreeSet(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;Vector;false;Vector;(Collection);;Element of Argument[0];Element of Argument[-1];value"
Vector out = null;
Collection in = List.of(source());
out = new Vector(in);
sink(getElement(out)); // $ hasValueFlow
}
{
// "java.util;WeakHashMap;false;WeakHashMap;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value"
WeakHashMap out = null;
Map in = Map.of(source(), null);
out = new WeakHashMap(in);
sink(getMapKey(out)); // $ hasValueFlow
}
{
// "java.util;WeakHashMap;false;WeakHashMap;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value"
WeakHashMap out = null;
Map in = Map.of(null, source());
out = new WeakHashMap(in);
sink(getMapValue(out)); // $ hasValueFlow
}
}
}

View File

@@ -0,0 +1,160 @@
import org.springframework.http.ResponseEntity;
import org.springframework.http.MediaType;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
public class SpringXSS {
@GetMapping
public static ResponseEntity<String> specificContentType(boolean safeContentType, boolean chainDirectly, String userControlled) {
ResponseEntity.BodyBuilder builder = ResponseEntity.ok();
if(safeContentType) {
if(chainDirectly) {
return builder.contentType(MediaType.TEXT_HTML).body(userControlled); // $xss
}
else {
ResponseEntity.BodyBuilder builder2 = builder.contentType(MediaType.TEXT_HTML);
return builder2.body(userControlled); // $xss
}
}
else {
if(chainDirectly) {
return builder.contentType(MediaType.APPLICATION_JSON).body(userControlled); // $SPURIOUS: xss
}
else {
ResponseEntity.BodyBuilder builder2 = builder.contentType(MediaType.APPLICATION_JSON);
return builder2.body(userControlled); // $SPURIOUS: xss
}
}
}
@GetMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
public static ResponseEntity<String> methodContentTypeSafe(String userControlled) {
return ResponseEntity.ok(userControlled);
}
@PostMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
public static ResponseEntity<String> methodContentTypeSafePost(String userControlled) {
return ResponseEntity.ok(userControlled);
}
@RequestMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
public static ResponseEntity<String> methodContentTypeSafeRequest(String userControlled) {
return ResponseEntity.ok(userControlled);
}
@GetMapping(value = "/xyz", produces = "application/json")
public static ResponseEntity<String> methodContentTypeSafeStringLiteral(String userControlled) {
return ResponseEntity.ok(userControlled);
}
@GetMapping(value = "/xyz", produces = MediaType.TEXT_HTML_VALUE)
public static ResponseEntity<String> methodContentTypeUnsafe(String userControlled) {
return ResponseEntity.ok(userControlled); // $MISSING: xss
}
@GetMapping(value = "/xyz", produces = "text/html")
public static ResponseEntity<String> methodContentTypeUnsafeStringLiteral(String userControlled) {
return ResponseEntity.ok(userControlled); // $xss
}
@GetMapping(value = "/xyz", produces = {MediaType.TEXT_HTML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public static ResponseEntity<String> methodContentTypeMaybeSafe(String userControlled) {
return ResponseEntity.ok(userControlled); // $xss
}
@GetMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
public static ResponseEntity<String> methodContentTypeSafeOverriddenWithUnsafe(String userControlled) {
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(userControlled); // $MISSING: xss
}
@GetMapping(value = "/xyz", produces = MediaType.TEXT_HTML_VALUE)
public static ResponseEntity<String> methodContentTypeUnsafeOverriddenWithSafe(String userControlled) {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(userControlled);
}
@GetMapping(value = "/xyz", produces = {"text/html", "application/json"})
public static ResponseEntity<String> methodContentTypeMaybeSafeStringLiterals(String userControlled, int constructionMethod) {
// Also try out some alternative constructors for the ResponseEntity:
switch(constructionMethod) {
case 0:
return ResponseEntity.ok(userControlled); // $xss
case 1:
return ResponseEntity.of(Optional.of(userControlled)); // $xss
case 2:
return ResponseEntity.ok().body(userControlled); // $xss
case 3:
return new ResponseEntity<String>(userControlled, HttpStatus.OK); // $xss
default:
return null;
}
}
@RestController
@RequestMapping(produces = {"application/json"})
private static class ClassContentTypeSafe {
@GetMapping(value = "/abc")
public ResponseEntity<String> test(String userControlled) {
return ResponseEntity.ok(userControlled); // $SPURIOUS: xss
}
@GetMapping(value = "/abc")
public String testDirectReturn(String userControlled) {
return userControlled; // $SPURIOUS: xss
}
@GetMapping(value = "/xyz", produces = {"text/html"})
public ResponseEntity<String> overridesWithUnsafe(String userControlled) {
return ResponseEntity.ok(userControlled); // $xss
}
@GetMapping(value = "/abc")
public ResponseEntity<String> overridesWithUnsafe2(String userControlled) {
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(userControlled); // $xss
}
}
@RestController
@RequestMapping(produces = {"text/html"})
private static class ClassContentTypeUnsafe {
@GetMapping(value = "/abc")
public ResponseEntity<String> test(String userControlled) {
return ResponseEntity.ok(userControlled); // $xss
}
@GetMapping(value = "/abc")
public String testDirectReturn(String userControlled) {
return userControlled; // $xss
}
@GetMapping(value = "/xyz", produces = {"application/json"})
public ResponseEntity<String> overridesWithSafe(String userControlled) {
return ResponseEntity.ok(userControlled); // $SPURIOUS: xss
}
@GetMapping(value = "/abc")
public ResponseEntity<String> overridesWithSafe2(String userControlled) {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(userControlled); // $SPURIOUS: xss
}
}
@GetMapping(value = "/abc")
public static ResponseEntity<String> entityWithNoMediaType(String userControlled) {
return ResponseEntity.ok(userControlled); // $xss
}
@GetMapping(value = "/abc")
public static String stringWithNoMediaType(String userControlled) {
return userControlled; // $xss
}
}

View File

@@ -1 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/:${testdir}/../../../../../stubs/springframework-5.3.8

Some files were not shown because too many files have changed in this diff Show More