Merge branch 'main' into java/update-mad-decls-after-triage-2024-01-24T10-05-04

This commit is contained in:
Stephan Brandauer
2024-01-24 14:55:20 +01:00
committed by GitHub
290 changed files with 2653 additions and 3284 deletions

View File

@@ -473,10 +473,6 @@
"ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll",
"python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll"
],
"Typo database": [
"javascript/ql/src/Expressions/TypoDatabase.qll",
"ql/ql/src/codeql_ql/style/TypoDatabase.qll"
],
"Swift declarations test file": [
"swift/ql/test/extractor-tests/declarations/declarations.swift",
"swift/ql/test/library-tests/ast/declarations.swift"

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Deleted many deprecated predicates and classes with uppercase `XML`, `SSA`, `SAL`, `SQL`, etc. in their names. Use the PascalCased versions instead.
* Deleted the deprecated `StrcatFunction` class, use `semmle.code.cpp.models.implementations.Strcat.qll` instead.

View File

@@ -380,9 +380,6 @@ class Class extends UserType {
*/
predicate isPod() { is_pod_class(underlyingElement(this)) }
/** DEPRECATED: Alias for isPod */
deprecated predicate isPOD() { this.isPod() }
/**
* Holds if this class, struct or union is a standard-layout class
* [N4140 9(7)]. Also holds for structs in C programs.

View File

@@ -104,9 +104,6 @@ predicate isPodClass03(Class c) {
)
}
/** DEPRECATED: Alias for isPodClass03 */
deprecated predicate isPODClass03 = isPodClass03/1;
/**
* Holds if `t` is a POD type, according to the rules specified in
* C++03 3.9(10):
@@ -126,6 +123,3 @@ predicate isPodType03(Type t) {
isPodType03(ut.(SpecifiedType).getUnspecifiedType())
)
}
/** DEPRECATED: Alias for isPodType03 */
deprecated predicate isPODType03 = isPodType03/1;

View File

@@ -32,9 +32,6 @@ class XmlLocatable extends @xmllocatable, TXmlLocatable {
string toString() { none() } // overridden in subclasses
}
/** DEPRECATED: Alias for XmlLocatable */
deprecated class XMLLocatable = XmlLocatable;
/**
* An `XmlParent` is either an `XmlElement` or an `XmlFile`,
* both of which can contain other elements.
@@ -95,9 +92,6 @@ class XmlParent extends @xmlparent {
string toString() { result = this.getName() }
}
/** DEPRECATED: Alias for XmlParent */
deprecated class XMLParent = XmlParent;
/** An XML file. */
class XmlFile extends XmlParent, File {
XmlFile() { xmlEncoding(this, _) }
@@ -119,14 +113,8 @@ class XmlFile extends XmlParent, File {
/** Gets a DTD associated with this XML file. */
XmlDtd getADtd() { xmlDTDs(result, _, _, _, this) }
/** DEPRECATED: Alias for getADtd */
deprecated XmlDtd getADTD() { result = this.getADtd() }
}
/** DEPRECATED: Alias for XmlFile */
deprecated class XMLFile = XmlFile;
/**
* An XML document type definition (DTD).
*
@@ -163,9 +151,6 @@ class XmlDtd extends XmlLocatable, @xmldtd {
}
}
/** DEPRECATED: Alias for XmlDtd */
deprecated class XMLDTD = XmlDtd;
/**
* An XML element in an XML file.
*
@@ -221,9 +206,6 @@ class XmlElement extends @xmlelement, XmlParent, XmlLocatable {
override string toString() { result = this.getName() }
}
/** DEPRECATED: Alias for XmlElement */
deprecated class XMLElement = XmlElement;
/**
* An attribute that occurs inside an XML element.
*
@@ -254,9 +236,6 @@ class XmlAttribute extends @xmlattribute, XmlLocatable {
override string toString() { result = this.getName() + "=" + this.getValue() }
}
/** DEPRECATED: Alias for XmlAttribute */
deprecated class XMLAttribute = XmlAttribute;
/**
* A namespace used in an XML file.
*
@@ -273,9 +252,6 @@ class XmlNamespace extends XmlLocatable, @xmlnamespace {
/** Gets the URI of this namespace. */
string getUri() { xmlNs(this, _, result, _) }
/** DEPRECATED: Alias for getUri */
deprecated string getURI() { result = this.getUri() }
/** Holds if this namespace has no prefix. */
predicate isDefault() { this.getPrefix() = "" }
@@ -286,9 +262,6 @@ class XmlNamespace extends XmlLocatable, @xmlnamespace {
}
}
/** DEPRECATED: Alias for XmlNamespace */
deprecated class XMLNamespace = XmlNamespace;
/**
* A comment in an XML file.
*
@@ -309,9 +282,6 @@ class XmlComment extends @xmlcomment, XmlLocatable {
override string toString() { result = this.getText() }
}
/** DEPRECATED: Alias for XmlComment */
deprecated class XMLComment = XmlComment;
/**
* A sequence of characters that occurs between opening and
* closing tags of an XML element, excluding other elements.
@@ -335,6 +305,3 @@ class XmlCharacters extends @xmlcharacters, XmlLocatable {
/** Gets a printable representation of this XML character sequence. */
override string toString() { result = this.getCharacters() }
}
/** DEPRECATED: Alias for XmlCharacters */
deprecated class XMLCharacters = XmlCharacters;

View File

@@ -5,9 +5,6 @@ class NullMacro extends Macro {
NullMacro() { this.getHead() = "NULL" }
}
/** DEPRECATED: Alias for NullMacro */
deprecated class NULLMacro = NullMacro;
/** A use of the NULL macro. */
class NULL extends Literal {
NULL() { exists(NullMacro nm | this = nm.getAnInvocation().getAnExpandedElement()) }

View File

@@ -1,22 +0,0 @@
import cpp
/**
* DEPRECATED: use `semmle.code.cpp.models.implementations.Strcat.qll` instead.
*
* A function that concatenates the string from its second argument
* to the string from its first argument, for example `strcat`.
*/
deprecated class StrcatFunction extends Function {
StrcatFunction() {
this.getName() =
[
"strcat", // strcat(dst, src)
"strncat", // strncat(dst, src, max_amount)
"wcscat", // wcscat(dst, src)
"_mbscat", // _mbscat(dst, src)
"wcsncat", // wcsncat(dst, src, max_amount)
"_mbsncat", // _mbsncat(dst, src, max_amount)
"_mbsncat_l" // _mbsncat_l(dst, src, max_amount, locale)
]
}
}

View File

@@ -1394,11 +1394,21 @@ abstract private class ExprNodeBase extends Node {
final Expr getExpr(int n) { result = this.getConvertedExpr(n).getUnconverted() }
}
/**
* Holds if there exists a dataflow node whose `asExpr(n)` should evaluate
* to `e`.
*/
private predicate exprNodeShouldBe(Expr e, int n) {
exprNodeShouldBeInstruction(_, e, n) or
exprNodeShouldBeOperand(_, e, n) or
exprNodeShouldBeIndirectOutNode(_, e, n)
}
private class InstructionExprNode extends ExprNodeBase, InstructionNode {
InstructionExprNode() {
exists(Expr e, int n |
exprNodeShouldBeInstruction(this, e, n) and
not exprNodeShouldBeInstruction(_, e, n + 1)
not exprNodeShouldBe(e, n + 1)
)
}
@@ -1409,7 +1419,7 @@ private class OperandExprNode extends ExprNodeBase, OperandNode {
OperandExprNode() {
exists(Expr e, int n |
exprNodeShouldBeOperand(this, e, n) and
not exprNodeShouldBeOperand(_, e, n + 1)
not exprNodeShouldBe(e, n + 1)
)
}

View File

@@ -609,7 +609,10 @@ class GlobalDefImpl extends DefOrUseImpl, TGlobalDefImpl {
*/
predicate adjacentDefRead(DefOrUse defOrUse1, UseOrPhi use) {
exists(IRBlock bb1, int i1, SourceVariable v |
defOrUse1.asDefOrUse().hasIndexInBlock(bb1, i1, v)
defOrUse1
.asDefOrUse()
.hasIndexInBlock(pragma[only_bind_out](bb1), pragma[only_bind_out](i1),
pragma[only_bind_out](v))
|
exists(IRBlock bb2, int i2, DefinitionExt def |
adjacentDefReadExt(pragma[only_bind_into](def), pragma[only_bind_into](bb1),
@@ -631,7 +634,11 @@ predicate adjacentDefRead(DefOrUse defOrUse1, UseOrPhi use) {
* flows to `useOrPhi`.
*/
private predicate globalDefToUse(GlobalDef globalDef, UseOrPhi useOrPhi) {
exists(IRBlock bb1, int i1, SourceVariable v | globalDef.hasIndexInBlock(bb1, i1, v) |
exists(IRBlock bb1, int i1, SourceVariable v |
globalDef
.hasIndexInBlock(pragma[only_bind_out](bb1), pragma[only_bind_out](i1),
pragma[only_bind_out](v))
|
exists(IRBlock bb2, int i2 |
adjacentDefReadExt(_, pragma[only_bind_into](bb1), pragma[only_bind_into](i1),
pragma[only_bind_into](bb2), pragma[only_bind_into](i2)) and

View File

@@ -1068,6 +1068,3 @@ module Ssa {
predicate hasUnreachedInstruction = Cached::hasUnreachedInstructionCached/1;
}
/** DEPRECATED: Alias for Ssa */
deprecated module SSA = Ssa;

View File

@@ -3,13 +3,6 @@ import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.reachability.Rea
import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.reachability.Dominance as Dominance
import semmle.code.cpp.ir.implementation.aliased_ssa.IR as NewIR
import semmle.code.cpp.ir.implementation.internal.TInstruction::AliasedSsaInstructions as SsaInstructions
/** DEPRECATED: Alias for SsaInstructions */
deprecated module SSAInstructions = SsaInstructions;
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
import AliasedSSA as Alias
import semmle.code.cpp.ir.implementation.internal.TOperand::AliasedSsaOperands as SsaOperands
/** DEPRECATED: Alias for SsaOperands */
deprecated module SSAOperands = SsaOperands;

View File

@@ -2,6 +2,3 @@ import semmle.code.cpp.ir.internal.IRCppLanguage as Language
import semmle.code.cpp.ir.implementation.raw.internal.IRConstruction as IRConstruction
import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction as UnaliasedSsa
import semmle.code.cpp.ir.implementation.aliased_ssa.internal.SSAConstruction as AliasedSsa
/** DEPRECATED: Alias for AliasedSsa */
deprecated module AliasedSSA = AliasedSsa;

View File

@@ -1068,6 +1068,3 @@ module Ssa {
predicate hasUnreachedInstruction = Cached::hasUnreachedInstructionCached/1;
}
/** DEPRECATED: Alias for Ssa */
deprecated module SSA = Ssa;

View File

@@ -4,13 +4,6 @@ import semmle.code.cpp.ir.implementation.raw.internal.reachability.Dominance as
import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as NewIR
import semmle.code.cpp.ir.implementation.raw.internal.IRConstruction as RawStage
import semmle.code.cpp.ir.implementation.internal.TInstruction::UnaliasedSsaInstructions as SsaInstructions
/** DEPRECATED: Alias for SsaInstructions */
deprecated module SSAInstructions = SsaInstructions;
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
import SimpleSSA as Alias
import semmle.code.cpp.ir.implementation.internal.TOperand::UnaliasedSsaOperands as SsaOperands
/** DEPRECATED: Alias for SsaOperands */
deprecated module SSAOperands = SsaOperands;

View File

@@ -27,10 +27,12 @@ private class StandardDeallocationFunction extends DeallocationFunction {
or
this.hasGlobalOrStdName([
// --- Windows Memory Management for Windows Drivers
"ExFreePoolWithTag", "ExDeleteTimer", "IoFreeMdl", "IoFreeWorkItem", "IoFreeErrorLogEntry",
"MmFreeContiguousMemory", "MmFreeContiguousMemorySpecifyCache", "MmFreeNonCachedMemory",
"MmFreeMappingAddress", "MmFreePagesFromMdl", "MmUnmapReservedMapping",
"MmUnmapLockedPages",
"ExFreePool", "ExFreePoolWithTag", "ExDeleteTimer", "IoFreeIrp", "IoFreeMdl",
"IoFreeErrorLogEntry", "IoFreeWorkItem", "MmFreeContiguousMemory",
"MmFreeContiguousMemorySpecifyCache", "MmFreeNonCachedMemory", "MmFreeMappingAddress",
"MmFreePagesFromMdl", "MmUnmapReservedMapping", "MmUnmapLockedPages",
"NdisFreeGenericObject", "NdisFreeMemory", "NdisFreeMemoryWithTag", "NdisFreeMdl",
"NdisFreeNetBufferListPool", "NdisFreeNetBufferPool",
// --- Windows Global / Local legacy allocation
"LocalFree", "GlobalFree", "LocalReAlloc", "GlobalReAlloc",
// --- Windows System Services allocation
@@ -47,6 +49,7 @@ private class StandardDeallocationFunction extends DeallocationFunction {
this.hasGlobalOrStdName([
// --- Windows Memory Management for Windows Drivers
"ExFreeToLookasideListEx", "ExFreeToPagedLookasideList", "ExFreeToNPagedLookasideList",
"NdisFreeMemoryWithTagPriority", "StorPortFreeMdl", "StorPortFreePool",
// --- NetBSD pool manager
"pool_put", "pool_cache_put"
]) and

View File

@@ -1,21 +1,11 @@
/**
* General library for finding flow from a pointer being freed to a user-specified sink
*/
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
private import semmle.code.cpp.ir.IR
/**
* Signature for a predicate that holds if `n.asExpr() = e` and `n` is a sink in
* the `FlowFromFreeConfig` module.
*/
private signature predicate isSinkSig(DataFlow::Node n, Expr e);
/**
* Holds if `dealloc` is a deallocation expression and `e` is an expression such
* that `isFree(_, e)` holds for some `isFree` predicate satisfying `isSinkSig`,
* and this source-sink pair should be excluded from the analysis.
*/
bindingset[dealloc, e]
private signature predicate isExcludedSig(DeallocationExpr dealloc, Expr e);
/**
* Holds if `(b1, i1)` strictly post-dominates `(b2, i2)`
*/
@@ -38,6 +28,31 @@ predicate strictlyDominates(IRBlock b1, int i1, IRBlock b2, int i2) {
b1.strictlyDominates(b2)
}
/**
* The signature for a module that is used to specify the inputs to the `FlowFromFree` module.
*/
signature module FlowFromFreeParamSig {
/**
* Holds if `n.asExpr() = e` and `n` is a sink in the `FlowFromFreeConfig`
* module.
*/
predicate isSink(DataFlow::Node n, Expr e);
/**
* Holds if `dealloc` is a deallocation expression and `e` is an expression such
* that `isFree(_, e)` holds for some `isFree` predicate satisfying `isSinkSig`,
* and this source-sink pair should be excluded from the analysis.
*/
bindingset[dealloc, e]
predicate isExcluded(DeallocationExpr dealloc, Expr e);
/**
* Holds if `sink` should be considered a `sink` when the source of flow is `source`.
*/
bindingset[source, sink]
default predicate sourceSinkIsRelated(DataFlow::Node source, DataFlow::Node sink) { any() }
}
/**
* Constructs a `FlowFromFreeConfig` module that can be used to find flow between
* a pointer being freed by some deallocation function, and a user-specified sink.
@@ -47,8 +62,8 @@ predicate strictlyDominates(IRBlock b1, int i1, IRBlock b2, int i2) {
* 1. The source dominates the sink, or
* 2. The sink post-dominates the source.
*/
module FlowFromFree<isSinkSig/2 isASink, isExcludedSig/2 isExcluded> {
module FlowFromFreeConfig implements DataFlow::StateConfigSig {
module FlowFromFree<FlowFromFreeParamSig P> {
private module FlowFromFreeConfig implements DataFlow::StateConfigSig {
class FlowState instanceof Expr {
FlowState() { isFree(_, _, this, _) }
@@ -59,20 +74,12 @@ module FlowFromFree<isSinkSig/2 isASink, isExcludedSig/2 isExcluded> {
pragma[inline]
predicate isSink(DataFlow::Node sink, FlowState state) {
exists(
Expr e, DataFlow::Node source, IRBlock b1, int i1, IRBlock b2, int i2,
DeallocationExpr dealloc
|
isASink(sink, e) and
exists(Expr e, DataFlow::Node source, DeallocationExpr dealloc |
P::isSink(sink, e) and
isFree(source, _, state, dealloc) and
e != state and
source.hasIndexInBlock(b1, i1) and
sink.hasIndexInBlock(b2, i2) and
not isExcluded(dealloc, e)
|
strictlyDominates(b1, i1, b2, i2)
or
strictlyPostDominates(b2, i2, b1, i1)
not P::isExcluded(dealloc, e) and
P::sourceSinkIsRelated(source, sink)
)
}
@@ -127,3 +134,38 @@ predicate isExFreePoolCall(FunctionCall fc, Expr e) {
fc.getTarget().hasGlobalName("ExFreePool")
)
}
/**
* Holds if either `source` strictly dominates `sink`, or `sink` strictly
* post-dominates `source`.
*/
bindingset[source, sink]
predicate defaultSourceSinkIsRelated(DataFlow::Node source, DataFlow::Node sink) {
exists(IRBlock b1, int i1, IRBlock b2, int i2 |
source.hasIndexInBlock(b1, i1) and
sink.hasIndexInBlock(b2, i2)
|
strictlyDominates(b1, i1, b2, i2)
or
strictlyPostDominates(b2, i2, b1, i1)
)
}
/**
* `dealloc1` is a deallocation expression, `e` is an expression that dereferences a
* pointer, and the `(dealloc1, e)` pair should be excluded by the `FlowFromFree` library.
*
* Note that `e` is not necessarily the expression deallocated by `dealloc1`. It will
* be bound to the second deallocation as identified by the `FlowFromFree` library.
*
* From https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmfreepagesfrommdl:
* "After calling MmFreePagesFromMdl, the caller must also call ExFreePool
* to release the memory that was allocated for the MDL structure."
*/
bindingset[dealloc1, e]
predicate isExcludedMmFreePageFromMdl(DeallocationExpr dealloc1, Expr e) {
exists(DeallocationExpr dealloc2 | isFree(_, _, e, dealloc2) |
dealloc1.(FunctionCall).getTarget().hasGlobalName("MmFreePagesFromMdl") and
isExFreePoolCall(dealloc2, _)
)
}

View File

@@ -0,0 +1,159 @@
/**
* General library for tracing Use After Free vulnerabilities.
*/
import cpp
private import semmle.code.cpp.security.flowafterfree.FlowAfterFree
private import semmle.code.cpp.ir.IR
/**
* Holds if `call` is a call to a function that obviously
* doesn't dereference its `i`'th argument.
*/
private predicate externalCallNeverDereferences(FormattingFunctionCall call, int arg) {
exists(int formatArg |
pragma[only_bind_out](call.getFormatArgument(formatArg)) =
pragma[only_bind_out](call.getArgument(arg)) and
call.getFormat().(FormatLiteral).getConvSpec(formatArg) != "%s"
)
}
/**
* Holds if `e` is a use. A use is a pointer dereference or a
* parameter to a call with no function definition.
* Uses in deallocation expressions (e.g., free) are excluded.
* Default isUse definition for an expression.
*/
predicate isUse0(Expr e) {
not isFree(_, _, e, _) and
(
// TODO: use DirectDefereferencedByOperation in Dereferenced.qll
e = any(PointerDereferenceExpr pde).getOperand()
or
e = any(PointerFieldAccess pfa).getQualifier()
or
e = any(ArrayExpr ae).getArrayBase()
or
e = any(Call call).getQualifier()
or
// Assume any function without a body will dereference the pointer
exists(int i, Call call, Function f |
e = call.getArgument(i) and
f = call.getTarget() and
not f.hasEntryPoint() and
// Exclude known functions we know won't dereference the pointer.
// For example, a call such as `printf("%p", myPointer)`.
not externalCallNeverDereferences(call, i)
)
)
}
private module ParameterSinks {
import semmle.code.cpp.ir.ValueNumbering
private predicate flowsToUse(DataFlow::Node n) {
isUse0(n.asExpr())
or
exists(DataFlow::Node succ |
flowsToUse(succ) and
DataFlow::localFlowStep(n, succ)
)
}
private predicate flowsFromParam(DataFlow::Node n) {
flowsToUse(n) and
(
n.asParameter().getUnspecifiedType() instanceof PointerType
or
exists(DataFlow::Node prev |
flowsFromParam(prev) and
DataFlow::localFlowStep(prev, n)
)
)
}
private predicate step(DataFlow::Node n1, DataFlow::Node n2) {
flowsFromParam(n1) and
flowsFromParam(n2) and
DataFlow::localFlowStep(n1, n2)
}
private predicate paramToUse(DataFlow::Node n1, DataFlow::Node n2) = fastTC(step/2)(n1, n2)
private predicate hasFlow(
DataFlow::Node source, InitializeParameterInstruction init, DataFlow::Node sink
) {
pragma[only_bind_out](source.asParameter()) = pragma[only_bind_out](init.getParameter()) and
paramToUse(source, sink) and
isUse0(sink.asExpr())
}
private InitializeParameterInstruction getAnAlwaysDereferencedParameter0() {
exists(DataFlow::Node source, DataFlow::Node sink, IRBlock b1, int i1, IRBlock b2, int i2 |
hasFlow(pragma[only_bind_into](source), result, pragma[only_bind_into](sink)) and
source.hasIndexInBlock(b1, pragma[only_bind_into](i1)) and
sink.hasIndexInBlock(b2, pragma[only_bind_into](i2)) and
strictlyPostDominates(b2, i2, b1, i1)
)
}
private CallInstruction getAnAlwaysReachedCallInstruction() {
exists(IRFunction f | result.getBlock().postDominates(f.getEntryBlock()))
}
pragma[nomagic]
private predicate callHasTargetAndArgument(Function f, int i, Instruction argument) {
exists(CallInstruction call |
call.getStaticCallTarget() = f and
call.getArgument(i) = argument and
call = getAnAlwaysReachedCallInstruction()
)
}
pragma[nomagic]
private predicate initializeParameterInFunction(Function f, int i) {
exists(InitializeParameterInstruction init |
pragma[only_bind_out](init.getEnclosingFunction()) = f and
init.hasIndex(i) and
init = getAnAlwaysDereferencedParameter()
)
}
pragma[nomagic]
private predicate alwaysDereferencedArgumentHasValueNumber(ValueNumber vn) {
exists(int i, Function f, Instruction argument |
callHasTargetAndArgument(f, i, argument) and
initializeParameterInFunction(pragma[only_bind_into](f), pragma[only_bind_into](i)) and
vn.getAnInstruction() = argument
)
}
InitializeParameterInstruction getAnAlwaysDereferencedParameter() {
result = getAnAlwaysDereferencedParameter0()
or
exists(ValueNumber vn |
alwaysDereferencedArgumentHasValueNumber(vn) and
vn.getAnInstruction() = result
)
}
}
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon
/**
* Holds if `n` represents the expression `e`, and `e` is a pointer that is
* guaranteed to be dereferenced (either because it's an operand of a
* dereference operation, or because it's an argument to a function that
* always dereferences the parameter).
*/
predicate isUse(DataFlow::Node n, Expr e) {
isUse0(e) and n.asExpr() = e
or
exists(CallInstruction call, InitializeParameterInstruction init |
n.asOperand().getDef().getUnconvertedResultExpression() = e and
pragma[only_bind_into](init) = ParameterSinks::getAnAlwaysDereferencedParameter() and
viableParamArg(call, DataFlow::instructionNode(init), n) and
pragma[only_bind_out](init.getEnclosingFunction()) =
pragma[only_bind_out](call.getStaticCallTarget())
)
}

View File

@@ -13,7 +13,7 @@
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
import FlowAfterFree
import semmle.code.cpp.security.flowafterfree.FlowAfterFree
import DoubleFree::PathGraph
/**
@@ -22,26 +22,15 @@ import DoubleFree::PathGraph
*/
predicate isFree(DataFlow::Node n, Expr e) { isFree(_, n, e, _) }
/**
* `dealloc1` is a deallocation expression and `e` is an expression such
* that is deallocated by a deallocation expression, and the `(dealloc1, e)` pair
* should be excluded by the `FlowFromFree` library.
*
* Note that `e` is not necessarily the expression deallocated by `dealloc1`. It will
* be bound to the second deallocation as identified by the `FlowFromFree` library.
*/
bindingset[dealloc1, e]
predicate isExcludeFreePair(DeallocationExpr dealloc1, Expr e) {
exists(DeallocationExpr dealloc2 | isFree(_, _, e, dealloc2) |
dealloc1.(FunctionCall).getTarget().hasGlobalName("MmFreePagesFromMdl") and
// From https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmfreepagesfrommdl:
// "After calling MmFreePagesFromMdl, the caller must also call ExFreePool
// to release the memory that was allocated for the MDL structure."
isExFreePoolCall(dealloc2, _)
)
module DoubleFreeParam implements FlowFromFreeParamSig {
predicate isSink = isFree/2;
predicate isExcluded = isExcludedMmFreePageFromMdl/2;
predicate sourceSinkIsRelated = defaultSourceSinkIsRelated/2;
}
module DoubleFree = FlowFromFree<isFree/2, isExcludeFreePair/2>;
module DoubleFree = FlowFromFree<DoubleFreeParam>;
from DoubleFree::PathNode source, DoubleFree::PathNode sink, DeallocationExpr dealloc, Expr e2
where

View File

@@ -14,170 +14,25 @@
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
import semmle.code.cpp.ir.IR
import FlowAfterFree
import UseAfterFree::PathGraph
import semmle.code.cpp.security.flowafterfree.FlowAfterFree
import semmle.code.cpp.security.flowafterfree.UseAfterFree
import UseAfterFreeTrace::PathGraph
/**
* Holds if `call` is a call to a function that obviously
* doesn't dereference its `i`'th argument.
*/
private predicate externalCallNeverDereferences(FormattingFunctionCall call, int arg) {
exists(int formatArg |
pragma[only_bind_out](call.getFormatArgument(formatArg)) =
pragma[only_bind_out](call.getArgument(arg)) and
call.getFormat().(FormatLiteral).getConvSpec(formatArg) != "%s"
)
module UseAfterFreeParam implements FlowFromFreeParamSig {
predicate isSink = isUse/2;
predicate isExcluded = isExcludedMmFreePageFromMdl/2;
predicate sourceSinkIsRelated = defaultSourceSinkIsRelated/2;
}
predicate isUse0(Expr e) {
not isFree(_, _, e, _) and
(
e = any(PointerDereferenceExpr pde).getOperand()
or
e = any(PointerFieldAccess pfa).getQualifier()
or
e = any(ArrayExpr ae).getArrayBase()
or
e = any(Call call).getQualifier()
or
// Assume any function without a body will dereference the pointer
exists(int i, Call call, Function f |
e = call.getArgument(i) and
f = call.getTarget() and
not f.hasEntryPoint() and
// Exclude known functions we know won't dereference the pointer.
// For example, a call such as `printf("%p", myPointer)`.
not externalCallNeverDereferences(call, i)
)
)
}
import UseAfterFreeParam
module ParameterSinks {
import semmle.code.cpp.ir.ValueNumbering
module UseAfterFreeTrace = FlowFromFree<UseAfterFreeParam>;
predicate flowsToUse(DataFlow::Node n) {
isUse0(n.asExpr())
or
exists(DataFlow::Node succ |
flowsToUse(succ) and
DataFlow::localFlowStep(n, succ)
)
}
private predicate flowsFromParam(DataFlow::Node n) {
flowsToUse(n) and
(
n.asParameter().getUnspecifiedType() instanceof PointerType
or
exists(DataFlow::Node prev |
flowsFromParam(prev) and
DataFlow::localFlowStep(prev, n)
)
)
}
private predicate step(DataFlow::Node n1, DataFlow::Node n2) {
flowsFromParam(n1) and
flowsFromParam(n2) and
DataFlow::localFlowStep(n1, n2)
}
private predicate paramToUse(DataFlow::Node n1, DataFlow::Node n2) = fastTC(step/2)(n1, n2)
private predicate hasFlow(
DataFlow::Node source, InitializeParameterInstruction init, DataFlow::Node sink
) {
pragma[only_bind_out](source.asParameter()) = pragma[only_bind_out](init.getParameter()) and
paramToUse(source, sink) and
isUse0(sink.asExpr())
}
private InitializeParameterInstruction getAnAlwaysDereferencedParameter0() {
exists(DataFlow::Node source, DataFlow::Node sink, IRBlock b1, int i1, IRBlock b2, int i2 |
hasFlow(pragma[only_bind_into](source), result, pragma[only_bind_into](sink)) and
source.hasIndexInBlock(b1, pragma[only_bind_into](i1)) and
sink.hasIndexInBlock(b2, pragma[only_bind_into](i2)) and
strictlyPostDominates(b2, i2, b1, i1)
)
}
private CallInstruction getAnAlwaysReachedCallInstruction() {
exists(IRFunction f | result.getBlock().postDominates(f.getEntryBlock()))
}
pragma[nomagic]
private predicate callHasTargetAndArgument(Function f, int i, Instruction argument) {
exists(CallInstruction call |
call.getStaticCallTarget() = f and
call.getArgument(i) = argument and
call = getAnAlwaysReachedCallInstruction()
)
}
pragma[nomagic]
private predicate initializeParameterInFunction(Function f, int i) {
exists(InitializeParameterInstruction init |
pragma[only_bind_out](init.getEnclosingFunction()) = f and
init.hasIndex(i) and
init = getAnAlwaysDereferencedParameter()
)
}
pragma[nomagic]
private predicate alwaysDereferencedArgumentHasValueNumber(ValueNumber vn) {
exists(int i, Function f, Instruction argument |
callHasTargetAndArgument(f, i, argument) and
initializeParameterInFunction(pragma[only_bind_into](f), pragma[only_bind_into](i)) and
vn.getAnInstruction() = argument
)
}
InitializeParameterInstruction getAnAlwaysDereferencedParameter() {
result = getAnAlwaysDereferencedParameter0()
or
exists(ValueNumber vn |
alwaysDereferencedArgumentHasValueNumber(vn) and
vn.getAnInstruction() = result
)
}
}
module IsUse {
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon
predicate isUse(DataFlow::Node n, Expr e) {
isUse0(e) and n.asExpr() = e
or
exists(CallInstruction call, InitializeParameterInstruction init |
n.asOperand().getDef().getUnconvertedResultExpression() = e and
pragma[only_bind_into](init) = ParameterSinks::getAnAlwaysDereferencedParameter() and
viableParamArg(call, DataFlow::instructionNode(init), n) and
pragma[only_bind_out](init.getEnclosingFunction()) =
pragma[only_bind_out](call.getStaticCallTarget())
)
}
}
import IsUse
/**
* `dealloc1` is a deallocation expression, `e` is an expression that dereferences a
* pointer, and the `(dealloc1, e)` pair should be excluded by the `FlowFromFree` library.
*/
bindingset[dealloc1, e]
predicate isExcludeFreeUsePair(DeallocationExpr dealloc1, Expr e) {
// From https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmfreepagesfrommdl:
// "After calling MmFreePagesFromMdl, the caller must also call ExFreePool
// to release the memory that was allocated for the MDL structure."
dealloc1.(FunctionCall).getTarget().hasGlobalName("MmFreePagesFromMdl") and
isExFreePoolCall(_, e)
}
module UseAfterFree = FlowFromFree<isUse/2, isExcludeFreeUsePair/2>;
from UseAfterFree::PathNode source, UseAfterFree::PathNode sink, DeallocationExpr dealloc
from UseAfterFreeTrace::PathNode source, UseAfterFreeTrace::PathNode sink, DeallocationExpr dealloc
where
UseAfterFree::flowPath(source, sink) and
UseAfterFreeTrace::flowPath(source, sink) and
isFree(source.getNode(), _, _, dealloc)
select sink.getNode(), source, sink, "Memory may have been previously freed by $@.", dealloc,
dealloc.toString()

View File

@@ -22,9 +22,6 @@ class SalMacro extends Macro {
}
}
/** DEPRECATED: Alias for SalMacro */
deprecated class SALMacro = SalMacro;
pragma[noinline]
private predicate isTopLevelMacroAccess(MacroAccess ma) { not exists(ma.getParentInvocation()) }
@@ -50,9 +47,6 @@ class SalAnnotation extends MacroInvocation {
}
}
/** DEPRECATED: Alias for SalAnnotation */
deprecated class SALAnnotation = SalAnnotation;
/**
* A SAL macro indicating that the return value of a function should always be
* checked.
@@ -63,9 +57,6 @@ class SalCheckReturn extends SalAnnotation {
}
}
/** DEPRECATED: Alias for SalCheckReturn */
deprecated class SALCheckReturn = SalCheckReturn;
/**
* A SAL macro indicating that a pointer variable or return value should not be
* `NULL`.
@@ -89,9 +80,6 @@ class SalNotNull extends SalAnnotation {
}
}
/** DEPRECATED: Alias for SalNotNull */
deprecated class SALNotNull = SalNotNull;
/**
* A SAL macro indicating that a value may be `NULL`.
*/
@@ -105,9 +93,6 @@ class SalMaybeNull extends SalAnnotation {
}
}
/** DEPRECATED: Alias for SalMaybeNull */
deprecated class SALMaybeNull = SalMaybeNull;
/**
* A parameter annotated by one or more SAL annotations.
*/
@@ -124,9 +109,6 @@ class SalParameter extends Parameter {
predicate isInOut() { a.getMacroName().toLowerCase().matches("%\\_inout%") }
}
/** DEPRECATED: Alias for SalParameter */
deprecated class SALParameter = SalParameter;
///////////////////////////////////////////////////////////////////////////////
// Implementation details
/**
@@ -199,9 +181,6 @@ class SalElement extends Element {
}
}
/** DEPRECATED: Alias for SalElement */
deprecated class SALElement = SalElement;
/** Holds if `file` contains a SAL annotation. */
pragma[noinline]
private predicate containsSalAnnotation(File file) { any(SalAnnotation a).getFile() = file }

View File

@@ -55,9 +55,6 @@ class SqlClientInfo extends SystemData {
override predicate isSensitive() { any() }
}
/** DEPRECATED: Alias for SqlClientInfo */
deprecated class SQLClientInfo = SqlClientInfo;
private predicate sqlConnectInfo(FunctionCall source, Expr use) {
(
source.getTarget().hasName("mysql_connect") or
@@ -77,9 +74,6 @@ class SqlConnectInfo extends SystemData {
override predicate isSensitive() { any() }
}
/** DEPRECATED: Alias for SqlConnectInfo */
deprecated class SQLConnectInfo = SqlConnectInfo;
private predicate posixSystemInfo(FunctionCall source, DataFlow::Node use) {
// size_t confstr(int name, char *buf, size_t len)
// - various OS / system strings, such as the libc version

View File

@@ -12,8 +12,6 @@ edges
| A.cpp:31:20:31:20 | c | A.cpp:31:14:31:21 | call to B [c] |
| A.cpp:41:5:41:6 | insert output argument | A.cpp:43:10:43:12 | *& ... |
| A.cpp:41:15:41:21 | new | A.cpp:41:5:41:6 | insert output argument |
| A.cpp:41:15:41:21 | new | A.cpp:41:5:41:6 | insert output argument |
| A.cpp:41:15:41:21 | new | A.cpp:41:15:41:21 | new |
| A.cpp:47:12:47:18 | new | A.cpp:48:20:48:20 | c |
| A.cpp:48:12:48:18 | *call to make [c] | A.cpp:49:10:49:10 | *b [c] |
| A.cpp:48:20:48:20 | c | A.cpp:29:23:29:23 | c |
@@ -22,7 +20,6 @@ edges
| A.cpp:55:5:55:5 | set output argument [c] | A.cpp:56:10:56:10 | *b [c] |
| A.cpp:55:12:55:19 | new | A.cpp:27:17:27:17 | c |
| A.cpp:55:12:55:19 | new | A.cpp:55:5:55:5 | set output argument [c] |
| A.cpp:55:12:55:19 | new | A.cpp:55:12:55:19 | new |
| A.cpp:56:10:56:10 | *b [c] | A.cpp:28:8:28:10 | *this [c] |
| A.cpp:56:10:56:10 | *b [c] | A.cpp:56:10:56:17 | call to get |
| A.cpp:57:11:57:24 | *new [c] | A.cpp:28:8:28:10 | *this [c] |
@@ -33,12 +30,10 @@ edges
| A.cpp:57:17:57:23 | new | A.cpp:57:17:57:23 | new |
| A.cpp:64:10:64:15 | *call to setOnB [c] | A.cpp:66:10:66:11 | *b2 [c] |
| A.cpp:64:21:64:28 | new | A.cpp:64:10:64:15 | *call to setOnB [c] |
| A.cpp:64:21:64:28 | new | A.cpp:64:21:64:28 | new |
| A.cpp:64:21:64:28 | new | A.cpp:85:26:85:26 | c |
| A.cpp:66:10:66:11 | *b2 [c] | A.cpp:66:10:66:14 | c |
| A.cpp:73:10:73:19 | *call to setOnBWrap [c] | A.cpp:75:10:75:11 | *b2 [c] |
| A.cpp:73:25:73:32 | new | A.cpp:73:10:73:19 | *call to setOnBWrap [c] |
| A.cpp:73:25:73:32 | new | A.cpp:73:25:73:32 | new |
| A.cpp:73:25:73:32 | new | A.cpp:78:27:78:27 | c |
| A.cpp:75:10:75:11 | *b2 [c] | A.cpp:75:10:75:14 | c |
| A.cpp:78:27:78:27 | c | A.cpp:81:21:81:21 | c |
@@ -770,7 +765,6 @@ nodes
| A.cpp:31:20:31:20 | c | semmle.label | c |
| A.cpp:41:5:41:6 | insert output argument | semmle.label | insert output argument |
| A.cpp:41:15:41:21 | new | semmle.label | new |
| A.cpp:41:15:41:21 | new | semmle.label | new |
| A.cpp:43:10:43:12 | *& ... | semmle.label | *& ... |
| A.cpp:47:12:47:18 | new | semmle.label | new |
| A.cpp:48:12:48:18 | *call to make [c] | semmle.label | *call to make [c] |
@@ -779,7 +773,6 @@ nodes
| A.cpp:49:10:49:13 | c | semmle.label | c |
| A.cpp:55:5:55:5 | set output argument [c] | semmle.label | set output argument [c] |
| A.cpp:55:12:55:19 | new | semmle.label | new |
| A.cpp:55:12:55:19 | new | semmle.label | new |
| A.cpp:56:10:56:10 | *b [c] | semmle.label | *b [c] |
| A.cpp:56:10:56:17 | call to get | semmle.label | call to get |
| A.cpp:57:10:57:32 | call to get | semmle.label | call to get |
@@ -789,12 +782,10 @@ nodes
| A.cpp:57:17:57:23 | new | semmle.label | new |
| A.cpp:64:10:64:15 | *call to setOnB [c] | semmle.label | *call to setOnB [c] |
| A.cpp:64:21:64:28 | new | semmle.label | new |
| A.cpp:64:21:64:28 | new | semmle.label | new |
| A.cpp:66:10:66:11 | *b2 [c] | semmle.label | *b2 [c] |
| A.cpp:66:10:66:14 | c | semmle.label | c |
| A.cpp:73:10:73:19 | *call to setOnBWrap [c] | semmle.label | *call to setOnBWrap [c] |
| A.cpp:73:25:73:32 | new | semmle.label | new |
| A.cpp:73:25:73:32 | new | semmle.label | new |
| A.cpp:75:10:75:11 | *b2 [c] | semmle.label | *b2 [c] |
| A.cpp:75:10:75:14 | c | semmle.label | c |
| A.cpp:78:6:78:15 | **setOnBWrap [c] | semmle.label | **setOnBWrap [c] |
@@ -1608,14 +1599,10 @@ subpaths
| simple.cpp:84:14:84:20 | *this [f2, f1] | simple.cpp:78:9:78:15 | *this [f2, f1] | simple.cpp:78:9:78:15 | *getf2f1 | simple.cpp:84:14:84:20 | call to getf2f1 |
#select
| A.cpp:43:10:43:12 | *& ... | A.cpp:41:15:41:21 | new | A.cpp:43:10:43:12 | *& ... | *& ... flows from $@ | A.cpp:41:15:41:21 | new | new |
| A.cpp:43:10:43:12 | *& ... | A.cpp:41:15:41:21 | new | A.cpp:43:10:43:12 | *& ... | *& ... flows from $@ | A.cpp:41:15:41:21 | new | new |
| A.cpp:49:10:49:13 | c | A.cpp:47:12:47:18 | new | A.cpp:49:10:49:13 | c | c flows from $@ | A.cpp:47:12:47:18 | new | new |
| A.cpp:56:10:56:17 | call to get | A.cpp:55:12:55:19 | new | A.cpp:56:10:56:17 | call to get | call to get flows from $@ | A.cpp:55:12:55:19 | new | new |
| A.cpp:56:10:56:17 | call to get | A.cpp:55:12:55:19 | new | A.cpp:56:10:56:17 | call to get | call to get flows from $@ | A.cpp:55:12:55:19 | new | new |
| A.cpp:57:10:57:32 | call to get | A.cpp:57:17:57:23 | new | A.cpp:57:10:57:32 | call to get | call to get flows from $@ | A.cpp:57:17:57:23 | new | new |
| A.cpp:66:10:66:14 | c | A.cpp:64:21:64:28 | new | A.cpp:66:10:66:14 | c | c flows from $@ | A.cpp:64:21:64:28 | new | new |
| A.cpp:66:10:66:14 | c | A.cpp:64:21:64:28 | new | A.cpp:66:10:66:14 | c | c flows from $@ | A.cpp:64:21:64:28 | new | new |
| A.cpp:75:10:75:14 | c | A.cpp:73:25:73:32 | new | A.cpp:75:10:75:14 | c | c flows from $@ | A.cpp:73:25:73:32 | new | new |
| A.cpp:75:10:75:14 | c | A.cpp:73:25:73:32 | new | A.cpp:75:10:75:14 | c | c flows from $@ | A.cpp:73:25:73:32 | new | new |
| A.cpp:107:12:107:16 | a | A.cpp:98:12:98:18 | new | A.cpp:107:12:107:16 | a | a flows from $@ | A.cpp:98:12:98:18 | new | new |
| A.cpp:120:12:120:16 | a | A.cpp:98:12:98:18 | new | A.cpp:120:12:120:16 | a | a flows from $@ | A.cpp:98:12:98:18 | new | new |

View File

@@ -1,11 +1,8 @@
| concat.cpp:23:27:23:27 | call to operator+ | concat.cpp:23:22:23:25 | str1 | concat.cpp:23:22:23:31 | call to operator+ |
| concat.cpp:23:27:23:27 | call to operator+ | concat.cpp:23:22:23:25 | str1 | concat.cpp:23:22:23:31 | call to operator+ |
| concat.cpp:23:27:23:27 | call to operator+ | concat.cpp:23:22:23:25 | str1 | concat.cpp:23:27:23:27 | call to operator+ |
| concat.cpp:23:27:23:27 | call to operator+ | concat.cpp:23:29:23:31 | | concat.cpp:23:22:23:31 | call to operator+ |
| concat.cpp:23:27:23:27 | call to operator+ | concat.cpp:23:29:23:31 | | concat.cpp:23:22:23:31 | call to operator+ |
| concat.cpp:23:27:23:27 | call to operator+ | concat.cpp:23:29:23:31 | | concat.cpp:23:27:23:27 | call to operator+ |
| concat.cpp:23:33:23:33 | call to operator+ | concat.cpp:23:35:23:38 | str2 | concat.cpp:23:22:23:38 | call to operator+ |
| concat.cpp:23:33:23:33 | call to operator+ | concat.cpp:23:35:23:38 | str2 | concat.cpp:23:22:23:38 | call to operator+ |
| concat.cpp:23:33:23:33 | call to operator+ | concat.cpp:23:35:23:38 | str2 | concat.cpp:23:33:23:33 | call to operator+ |
| concat.cpp:23:40:23:40 | call to operator+ | concat.cpp:23:42:23:45 | str3 | concat.cpp:23:40:23:40 | call to operator+ |
| concat.cpp:47:8:47:8 | call to operator<< | concat.cpp:47:11:47:14 | str1 | concat.cpp:47:8:47:17 | call to operator<< |

View File

@@ -89,6 +89,7 @@
| test_free.cpp:216:10:216:10 | a |
| test_free.cpp:220:10:220:10 | a |
| test_free.cpp:227:24:227:45 | memory_descriptor_list |
| test_free.cpp:228:16:228:37 | memory_descriptor_list |
| test_free.cpp:233:14:233:15 | * ... |
| test_free.cpp:239:14:239:15 | * ... |
| test_free.cpp:245:10:245:11 | * ... |

View File

@@ -116,9 +116,7 @@ namespace Semmle.Autobuild.CSharp.Tests
string? IBuildActions.GetEnvironmentVariable(string name)
{
if (!GetEnvironmentVariable.TryGetValue(name, out var ret))
throw new ArgumentException("Missing GetEnvironmentVariable " + name);
GetEnvironmentVariable.TryGetValue(name, out var ret);
return ret;
}

View File

@@ -11,6 +11,7 @@ namespace Semmle.Autobuild.CSharp
/// </summary>
public class CSharpAutobuildOptions : AutobuildOptionsShared
{
private const string buildModeEnvironmentVariable = "CODEQL_EXTRACTOR_CSHARP_BUILD_MODE";
private const string extractorOptionPrefix = "CODEQL_EXTRACTOR_CSHARP_OPTION_";
public bool Buildless { get; }
@@ -25,7 +26,8 @@ namespace Semmle.Autobuild.CSharp
public CSharpAutobuildOptions(IBuildActions actions) : base(actions)
{
Buildless = actions.GetEnvironmentVariable(lgtmPrefix + "BUILDLESS").AsBool("buildless", false) ||
actions.GetEnvironmentVariable(extractorOptionPrefix + "BUILDLESS").AsBool("buildless", false);
actions.GetEnvironmentVariable(extractorOptionPrefix + "BUILDLESS").AsBool("buildless", false) ||
actions.GetEnvironmentVariable(buildModeEnvironmentVariable)?.ToLower() == "none";
}
}

View File

@@ -650,12 +650,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
private bool RestoreProject(string project, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, string? pathToNugetConfig = null) =>
dotnet.RestoreProjectToDirectory(project, packageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching, out assets, pathToNugetConfig);
private bool RestoreSolution(string solution, out IEnumerable<string> projects, out IEnumerable<string> assets) =>
dotnet.RestoreSolutionToDirectory(solution, packageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: true, out projects, out assets);
/// <summary>
/// Executes `dotnet restore` on all solution files in solutions.
/// As opposed to RestoreProjects this is not run in parallel using PLINQ
@@ -670,7 +664,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var assetFiles = new List<string>();
var projects = solutions.SelectMany(solution =>
{
RestoreSolution(solution, out var restoredProjects, out var a);
dotnet.RestoreSolutionToDirectory(solution, packageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: true, out var restoredProjects, out var a);
assetFiles.AddRange(a);
return restoredProjects;
});
@@ -689,7 +683,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var assetFiles = new List<string>();
Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = options.Threads }, project =>
{
RestoreProject(project, forceDotnetRefAssemblyFetching: true, out var a);
dotnet.RestoreProjectToDirectory(project, packageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: true, out var a, out var _);
assetFiles.AddRange(a);
});
assets = assetFiles;
@@ -736,11 +730,21 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return;
}
dotnet.RestoreProjectToDirectory(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: false, out var _, pathToNugetConfig: nugetConfig);
// TODO: the restore might fail, we could retry with a prerelease (*-* instead of *) version of the package.
success = dotnet.RestoreProjectToDirectory(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: false, out var _, out var outputLines, pathToNugetConfig: nugetConfig);
if (!success)
{
progressMonitor.FailedToRestoreNugetPackage(package);
if (outputLines?.Any(s => s.Contains("NU1301")) == true)
{
// Restore could not be completed because the listed source is unavailable. Try without the nuget.config:
success = dotnet.RestoreProjectToDirectory(tempDir.DirInfo.FullName, missingPackageDirectory.DirInfo.FullName, forceDotnetRefAssemblyFetching: false, out var _, out var _, pathToNugetConfig: null, force: true);
}
// TODO: the restore might fail, we could retry with a prerelease (*-* instead of *) version of the package.
if (!success)
{
progressMonitor.FailedToRestoreNugetPackage(package);
}
}
});

View File

@@ -72,7 +72,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private static IEnumerable<string> GetRestoredProjects(IEnumerable<string> lines) =>
GetFirstGroupOnMatch(RestoredProjectRegex(), lines);
public bool RestoreProjectToDirectory(string projectFile, string packageDirectory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, string? pathToNugetConfig = null)
public bool RestoreProjectToDirectory(string projectFile, string packageDirectory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, out IList<string> outputLines, string? pathToNugetConfig = null, bool force = false)
{
var args = GetRestoreArgs(projectFile, packageDirectory, forceDotnetRefAssemblyFetching);
if (pathToNugetConfig != null)
@@ -80,8 +80,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
args += $" --configfile \"{pathToNugetConfig}\"";
}
var success = dotnetCliInvoker.RunCommand(args, out var output);
assets = success ? GetAssetsFilePaths(output) : Array.Empty<string>();
if (force)
{
args += " --force";
}
var success = dotnetCliInvoker.RunCommand(args, out outputLines);
assets = success ? GetAssetsFilePaths(outputLines) : Array.Empty<string>();
return success;
}

View File

@@ -4,7 +4,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
{
internal interface IDotNet
{
bool RestoreProjectToDirectory(string project, string directory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, string? pathToNugetConfig = null);
bool RestoreProjectToDirectory(string project, string directory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, out IList<string> outputLines, string? pathToNugetConfig = null, bool force = false);
bool RestoreSolutionToDirectory(string solutionFile, string packageDirectory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> projects, out IEnumerable<string> assets);
bool New(string folder);
bool AddPackage(string folder, string package);

View File

@@ -101,7 +101,7 @@ namespace Semmle.Extraction.Tests
var dotnet = MakeDotnet(dotnetCliInvoker);
// Execute
dotnet.RestoreProjectToDirectory("myproject.csproj", "mypackages", false, out var assets);
dotnet.RestoreProjectToDirectory("myproject.csproj", "mypackages", false, out var assets, out var _);
// Verify
var lastArgs = dotnetCliInvoker.GetLastArgs();
@@ -116,7 +116,7 @@ namespace Semmle.Extraction.Tests
var dotnet = MakeDotnet(dotnetCliInvoker);
// Execute
dotnet.RestoreProjectToDirectory("myproject.csproj", "mypackages", false, out var assets, "myconfig.config");
dotnet.RestoreProjectToDirectory("myproject.csproj", "mypackages", false, out var assets, out var _, pathToNugetConfig: "myconfig.config");
// Verify
var lastArgs = dotnetCliInvoker.GetLastArgs();
@@ -126,6 +126,24 @@ namespace Semmle.Extraction.Tests
Assert.Contains("/path/to/project2.assets.json", assets);
}
[Fact]
public void TestDotnetRestoreProjectToDirectory3()
{
// Setup
var dotnetCliInvoker = new DotNetCliInvokerStub(MakeDotnetRestoreOutput());
var dotnet = MakeDotnet(dotnetCliInvoker);
// Execute
dotnet.RestoreProjectToDirectory("myproject.csproj", "mypackages", false, out var assets, out var _, pathToNugetConfig: "myconfig.config", force: true);
// Verify
var lastArgs = dotnetCliInvoker.GetLastArgs();
Assert.Equal("restore --no-dependencies \"myproject.csproj\" --packages \"mypackages\" /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile \"myconfig.config\" --force", lastArgs);
Assert.Equal(2, assets.Count());
Assert.Contains("/path/to/project.assets.json", assets);
Assert.Contains("/path/to/project2.assets.json", assets);
}
[Fact]
public void TestDotnetRestoreSolutionToDirectory1()
{

View File

@@ -19,9 +19,10 @@ namespace Semmle.Extraction.Tests
public bool New(string folder) => true;
public bool RestoreProjectToDirectory(string project, string directory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, string? pathToNugetConfig = null)
public bool RestoreProjectToDirectory(string project, string directory, bool forceDotnetRefAssemblyFetching, out IEnumerable<string> assets, out IList<string> outputLines, string? pathToNugetConfig = null, bool force = false)
{
assets = Array.Empty<string>();
outputLines = Array.Empty<string>();
return true;
}

View File

@@ -0,0 +1 @@
| newtonsoft.json/13.0.3/lib/net6.0/Newtonsoft.Json.dll |

View File

@@ -0,0 +1,11 @@
import csharp
private string getPath(Assembly a) {
not a.getCompilation().getOutputAssembly() = a and
exists(string s | s = a.getFile().getAbsolutePath() |
result = s.substring(s.indexOf("newtonsoft.json"), s.length())
)
}
from Assembly a
select getPath(a)

View File

@@ -0,0 +1,6 @@
class Program
{
static void Main(string[] args)
{
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="x" value="https://abc.abc/packages/" />
</packageSources>
</configuration>

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<Target Name="DeleteBinObjFolders" BeforeTargets="Clean">
<RemoveDir Directories=".\bin" />
<RemoveDir Directories=".\obj" />
</Target>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "proj", "proj\proj.csproj", "{6ED00460-7666-4AE9-A405-4B6C8B02279A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4ED55A1C-066C-43DF-B32E-7EAA035985EE}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,3 @@
from create_database_utils import *
run_codeql_database_create([], lang="csharp", extra_args=["--extractor-option=buildless=true", "--extractor-option=cil=false"])

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added a new library `semmle.code.csharp.security.dataflow.flowsources.FlowSources`, which provides a new class `ThreatModelFlowSource`. The `ThreatModelFlowSource` class can be used to include sources which match the current *threat model* configuration.

View File

@@ -0,0 +1,9 @@
---
category: minorAnalysis
---
* Deleted many deprecated predicates and classes with uppercase `SSL`, `XML`, `URI`, `SSA` etc. in their names. Use the PascalCased versions instead.
* Deleted the deprecated `getALocalFlowSucc` predicate and `TaintType` class from the dataflow library.
* Deleted the deprecated `Newobj` and `Rethrow` classes, use `NewObj` and `ReThrow` instead.
* Deleted the deprecated `getAFirstRead`, `hasAdjacentReads`, `lastRefBeforeRedef`, and `hasLastInputRef` predicates from the SSA library.
* Deleted the deprecated `getAReachableRead` predicate from the `AssignableRead` and `VariableRead` classes.
* Deleted the deprecated `hasQualifiedName` predicate from the `NamedElement` class.

View File

@@ -39,9 +39,6 @@ predicate maybeUsedInFnvFunction(Variable v, Operation xor, Operation mul, LoopS
loop.getAChild*() = xor.getEnclosingStmt()
}
/** DEPRECATED: Alias for maybeUsedInFnvFunction */
deprecated predicate maybeUsedInFNVFunction = maybeUsedInFnvFunction/4;
/**
* Holds if the arguments are used in a way that resembles an Elf-Hash hash function
* where there is a loop statement `loop` where the variable `v` is used in an xor `xor` expression

View File

@@ -10,6 +10,7 @@ dependencies:
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/ssa: ${workspace}
codeql/threat-models: ${workspace}
codeql/tutorial: ${workspace}
codeql/util: ${workspace}
dataExtensions:

View File

@@ -89,16 +89,10 @@ class FormsElement extends XmlElement {
result = this.getAttribute("requireSSL").getValue().trim().toLowerCase()
}
/** DEPRECATED: Alias for getRequireSsl */
deprecated string getRequireSSL() { result = this.getRequireSsl() }
/**
* Holds if `requireSSL` value is true.
*/
predicate isRequireSsl() { this.getRequireSsl() = "true" }
/** DEPRECATED: Alias for isRequireSsl */
deprecated predicate isRequireSSL() { this.isRequireSsl() }
}
/** A `<httpCookies>` tag in an ASP.NET configuration file. */
@@ -124,9 +118,6 @@ class HttpCookiesElement extends XmlElement {
result = this.getAttribute("requireSSL").getValue().trim().toLowerCase()
}
/** DEPRECATED: Alias for getRequireSsl */
deprecated string getRequireSSL() { result = this.getRequireSsl() }
/**
* Holds if there is any chance that `requireSSL` is set to `true` either globally or for Forms.
*/
@@ -136,9 +127,6 @@ class HttpCookiesElement extends XmlElement {
not this.getRequireSsl() = "false" and // not set all, i.e. default
exists(FormsElement forms | forms.getFile() = this.getFile() | forms.isRequireSsl())
}
/** DEPRECATED: Alias for isRequireSsl */
deprecated predicate isRequireSSL() { this.isRequireSsl() }
}
/** A `Transform` attribute in a Web.config transformation file. */

View File

@@ -16,23 +16,6 @@ class DataFlowNode extends @cil_dataflow_node {
/** Gets the type of this data flow node. */
Type getType() { none() }
/**
* Holds if this node flows to `sink` in one step.
* `tt` is the tainting that occurs during this step.
*/
deprecated predicate getALocalFlowSucc(DataFlowNode sink, TaintType tt) {
localExactStep(this, sink) and tt = TExactValue()
or
localTaintStep(this, sink) and tt = TTaintedValue()
}
deprecated private predicate flowsToStep(DataFlowNode sink) {
this.getALocalFlowSucc(sink, TExactValue())
}
/** Holds if this node flows to `sink` in zero or more steps. */
deprecated predicate flowsTo(DataFlowNode sink) { this.flowsToStep*(sink) }
/** Gets the method that contains this dataflow node. */
Method getMethod() { none() }
@@ -40,77 +23,6 @@ class DataFlowNode extends @cil_dataflow_node {
Location getLocation() { none() }
}
deprecated private newtype TTaintType =
TExactValue() or
TTaintedValue()
/** Describes how data is tainted. */
deprecated class TaintType extends TTaintType {
string toString() {
this = TExactValue() and result = "exact"
or
this = TTaintedValue() and result = "tainted"
}
}
/** A taint type where the data is untainted. */
deprecated class Untainted extends TaintType, TExactValue { }
/** A taint type where the data is tainted. */
deprecated class Tainted extends TaintType, TTaintedValue { }
deprecated private predicate localFlowPhiInput(DataFlowNode input, Ssa::PhiNode phi) {
exists(Ssa::Definition def, BasicBlock bb, int i | phi.hasLastInputRef(def, bb, i) |
def.definesAt(_, bb, i) and
input = def.getVariableUpdate().getSource()
or
input =
any(ReadAccess ra |
bb.getNode(i) = ra and
ra.getTarget() = def.getSourceVariable()
)
)
or
exists(Ssa::PhiNode mid, BasicBlock bb, int i |
localFlowPhiInput(input, mid) and
phi.hasLastInputRef(mid, bb, i) and
mid.definesAt(_, bb, i)
)
}
deprecated private predicate localExactStep(DataFlowNode src, DataFlowNode sink) {
src = sink.(Opcodes::Dup).getAnOperand()
or
exists(Ssa::Definition def, VariableUpdate vu |
vu = def.getVariableUpdate() and
src = vu.getSource() and
sink = def.getAFirstRead()
)
or
any(Ssa::Definition def).hasAdjacentReads(src, sink)
or
exists(Ssa::PhiNode phi |
localFlowPhiInput(src, phi) and
sink = phi.getAFirstRead()
)
or
src = sink.(Conversion).getExpr()
or
src = sink.(WriteAccess).getExpr()
or
src = sink.(Method).getAnImplementation().getAnInstruction().(Return)
or
src = sink.(Return).getExpr()
or
src = sink.(ConditionalBranch).getAnOperand()
}
deprecated private predicate localTaintStep(DataFlowNode src, DataFlowNode sink) {
src = sink.(BinaryArithmeticExpr).getAnOperand() or
src = sink.(Opcodes::Neg).getOperand() or
src = sink.(UnaryBitwiseOperation).getOperand()
}
/** A node that updates a variable. */
abstract class VariableUpdate extends DataFlowNode {
/** Gets the value assigned, if any. */

View File

@@ -788,9 +788,6 @@ module Opcodes {
}
}
/** DEPRECATED: Alias for NewObj */
deprecated class Newobj = NewObj;
/** An `initobj` instruction. */
class Initobj extends Instruction, @cil_initobj {
override string getOpcodeName() { result = "initobj" }
@@ -854,9 +851,6 @@ module Opcodes {
override string getOpcodeName() { result = "rethrow" }
}
/** DEPRECATED: Alias for ReThrow */
deprecated class Rethrow = ReThrow;
/** A `ldlen` instruction. */
class Ldlen extends UnaryExpr, @cil_ldlen {
override string getOpcodeName() { result = "ldlen" }

View File

@@ -23,14 +23,6 @@ module Ssa {
)
}
/** Gets a first read of this SSA definition. */
deprecated final ReadAccess getAFirstRead() { result = SsaImpl::getAFirstRead(this) }
/** Holds if `first` and `second` are adjacent reads of this SSA definition. */
deprecated final predicate hasAdjacentReads(ReadAccess first, ReadAccess second) {
SsaImpl::hasAdjacentReads(this, first, second)
}
private Definition getAPhiInput() { result = this.(PhiNode).getAnInput() }
/**
@@ -52,15 +44,5 @@ module Ssa {
/** Gets an input to this phi node. */
final Definition getAnInput() { result = SsaImpl::getAPhiInput(this) }
/**
* Holds if if `def` is an input to this phi node, and a reference to `def` at
* index `i` in basic block `bb` can reach this phi node without going through
* other references.
*/
deprecated final predicate hasLastInputRef(Definition def, BasicBlock bb, int i) {
SsaImpl::lastRefRedef(def, bb, i, this) and
def = SsaImpl::getAPhiInput(this)
}
}
}

View File

@@ -77,26 +77,6 @@ import Cached
private module Deprecated {
private import CIL
deprecated ReadAccess getAFirstRead(Definition def) {
exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
def.definesAt(_, bb1, i1) and
adjacentDefRead(def, bb1, i1, bb2, i2) and
result = bb2.getNode(i2)
)
}
deprecated predicate hasAdjacentReads(Definition def, ReadAccess first, ReadAccess second) {
exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
first = bb1.getNode(i1) and
adjacentDefRead(def, bb1, i1, bb2, i2) and
second = bb2.getNode(i2)
)
}
deprecated predicate lastRefBeforeRedef(Definition def, BasicBlock bb, int i, Definition next) {
lastRefRedef(def, bb, i, next)
}
}
import Deprecated

View File

@@ -117,15 +117,6 @@ class AssignableRead extends AssignableAccess {
cfn = this.getAnAdjacentReadSameVar()
)
}
/**
* Gets a reachable read of the same underlying assignable. That is, a read
* that can be reached from this read, and which is guaranteed to read the
* same value.
*
* This is the transitive closure of `getANextRead()`.
*/
deprecated AssignableRead getAReachableRead() { result = this.getANextRead+() }
}
/**
@@ -489,15 +480,6 @@ class AssignableDefinition extends TAssignableDefinition {
)
}
/**
* Gets a reachable read of the same underlying assignable. That is, a read
* that can be reached from this definition, and which is guaranteed to read
* the value assigned in this definition.
*
* This is the equivalent with `getAFirstRead().getANextRead*()`.
*/
deprecated AssignableRead getAReachableRead() { result = this.getAFirstRead().getANextRead*() }
/** Gets a textual representation of this assignable definition. */
string toString() { none() }

View File

@@ -32,9 +32,6 @@ class XmlLocatable extends @xmllocatable, TXmlLocatable {
string toString() { none() } // overridden in subclasses
}
/** DEPRECATED: Alias for XmlLocatable */
deprecated class XMLLocatable = XmlLocatable;
/**
* An `XmlParent` is either an `XmlElement` or an `XmlFile`,
* both of which can contain other elements.
@@ -95,9 +92,6 @@ class XmlParent extends @xmlparent {
string toString() { result = this.getName() }
}
/** DEPRECATED: Alias for XmlParent */
deprecated class XMLParent = XmlParent;
/** An XML file. */
class XmlFile extends XmlParent, File {
XmlFile() { xmlEncoding(this, _) }
@@ -119,14 +113,8 @@ class XmlFile extends XmlParent, File {
/** Gets a DTD associated with this XML file. */
XmlDtd getADtd() { xmlDTDs(result, _, _, _, this) }
/** DEPRECATED: Alias for getADtd */
deprecated XmlDtd getADTD() { result = this.getADtd() }
}
/** DEPRECATED: Alias for XmlFile */
deprecated class XMLFile = XmlFile;
/**
* An XML document type definition (DTD).
*
@@ -163,9 +151,6 @@ class XmlDtd extends XmlLocatable, @xmldtd {
}
}
/** DEPRECATED: Alias for XmlDtd */
deprecated class XMLDTD = XmlDtd;
/**
* An XML element in an XML file.
*
@@ -221,9 +206,6 @@ class XmlElement extends @xmlelement, XmlParent, XmlLocatable {
override string toString() { result = this.getName() }
}
/** DEPRECATED: Alias for XmlElement */
deprecated class XMLElement = XmlElement;
/**
* An attribute that occurs inside an XML element.
*
@@ -254,9 +236,6 @@ class XmlAttribute extends @xmlattribute, XmlLocatable {
override string toString() { result = this.getName() + "=" + this.getValue() }
}
/** DEPRECATED: Alias for XmlAttribute */
deprecated class XMLAttribute = XmlAttribute;
/**
* A namespace used in an XML file.
*
@@ -273,9 +252,6 @@ class XmlNamespace extends XmlLocatable, @xmlnamespace {
/** Gets the URI of this namespace. */
string getUri() { xmlNs(this, _, result, _) }
/** DEPRECATED: Alias for getUri */
deprecated string getURI() { result = this.getUri() }
/** Holds if this namespace has no prefix. */
predicate isDefault() { this.getPrefix() = "" }
@@ -286,9 +262,6 @@ class XmlNamespace extends XmlLocatable, @xmlnamespace {
}
}
/** DEPRECATED: Alias for XmlNamespace */
deprecated class XMLNamespace = XmlNamespace;
/**
* A comment in an XML file.
*
@@ -309,9 +282,6 @@ class XmlComment extends @xmlcomment, XmlLocatable {
override string toString() { result = this.getText() }
}
/** DEPRECATED: Alias for XmlComment */
deprecated class XMLComment = XmlComment;
/**
* A sequence of characters that occurs between opening and
* closing tags of an XML element, excluding other elements.
@@ -335,6 +305,3 @@ class XmlCharacters extends @xmlcharacters, XmlLocatable {
/** Gets a printable representation of this XML character sequence. */
override string toString() { result = this.getCharacters() }
}
/** DEPRECATED: Alias for XmlCharacters */
deprecated class XMLCharacters = XmlCharacters;

View File

@@ -173,10 +173,6 @@ class VariableAccess extends AssignableAccess, @variable_access_expr {
*/
class VariableRead extends VariableAccess, AssignableRead {
override VariableRead getANextRead() { result = AssignableRead.super.getANextRead() }
deprecated override VariableRead getAReachableRead() {
result = AssignableRead.super.getAReachableRead()
}
}
/**
@@ -201,10 +197,6 @@ class LocalScopeVariableAccess extends VariableAccess, @local_scope_variable_acc
*/
class LocalScopeVariableRead extends LocalScopeVariableAccess, VariableRead {
override LocalScopeVariableRead getANextRead() { result = VariableRead.super.getANextRead() }
deprecated override LocalScopeVariableRead getAReachableRead() {
result = VariableRead.super.getAReachableRead()
}
}
/**
@@ -243,10 +235,6 @@ class ParameterAccess extends LocalScopeVariableAccess, @parameter_access_expr {
*/
class ParameterRead extends ParameterAccess, LocalScopeVariableRead {
override ParameterRead getANextRead() { result = LocalScopeVariableRead.super.getANextRead() }
deprecated override ParameterRead getAReachableRead() {
result = LocalScopeVariableRead.super.getAReachableRead()
}
}
/**
@@ -298,10 +286,6 @@ class LocalVariableAccess extends LocalScopeVariableAccess, @local_variable_acce
*/
class LocalVariableRead extends LocalVariableAccess, LocalScopeVariableRead {
override LocalVariableRead getANextRead() { result = LocalScopeVariableRead.super.getANextRead() }
deprecated override LocalVariableRead getAReachableRead() {
result = LocalScopeVariableRead.super.getAReachableRead()
}
}
/**
@@ -443,10 +427,6 @@ class PropertyAccess extends AssignableMemberAccess, PropertyAccessExpr {
*/
class PropertyRead extends PropertyAccess, AssignableRead {
override PropertyRead getANextRead() { result = AssignableRead.super.getANextRead() }
deprecated override PropertyRead getAReachableRead() {
result = AssignableRead.super.getAReachableRead()
}
}
/**
@@ -584,10 +564,6 @@ class IndexerAccess extends AssignableMemberAccess, ElementAccess, IndexerAccess
*/
class IndexerRead extends IndexerAccess, ElementRead {
override IndexerRead getANextRead() { result = ElementRead.super.getANextRead() }
deprecated override IndexerRead getAReachableRead() {
result = ElementRead.super.getAReachableRead()
}
}
/**

View File

@@ -12,6 +12,7 @@ private import semmle.code.csharp.frameworks.Sql
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl::Public
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl::Private
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import semmle.code.csharp.security.dataflow.flowsources.Stored as Stored
/**
* Definitions relating to the `System.ComponentModel.DataAnnotations`
@@ -44,7 +45,7 @@ module EntityFramework {
}
/** A taint source where the data has come from a mapped property stored in the database. */
class StoredFlowSource extends DataFlow::Node {
class StoredFlowSource extends Stored::DatabaseInputSource {
StoredFlowSource() {
this.asExpr() = any(PropertyRead read | read.getTarget() instanceof MappedProperty)
}

View File

@@ -134,7 +134,7 @@ class ValidFormatString extends StringLiteral {
result = this.getValue().regexpFind(getValidFormatTokenRegex(), _, outPosition)
}
/**Gets the insert number at the given position in the string. */
/** Gets the insert number at the given position in the string. */
int getInsert(int position) {
result = this.getToken(position).regexpCapture(getFormatInsertRegex(), 1).toInt()
}

View File

@@ -6,6 +6,7 @@ import csharp
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.Sql
private import semmle.code.csharp.security.dataflow.flowsources.Stored as Stored
/** Definitions relating to the `NHibernate` package. */
module NHibernate {
@@ -86,7 +87,7 @@ module NHibernate {
}
/** A taint source where the data has come from a mapped property stored in the database. */
class StoredFlowSource extends DataFlow::Node {
class StoredFlowSource extends Stored::DatabaseInputSource {
StoredFlowSource() {
this.asExpr() = any(PropertyRead read | read.getTarget() instanceof MappedProperty)
}

View File

@@ -123,9 +123,6 @@ class MicrosoftOwinIOwinRequestClass extends Class {
result = this.getAProperty() and
result.hasName("Uri")
}
/** DEPRECATED: Alias for getUriProperty */
deprecated Property getURIProperty() { result = this.getUriProperty() }
}
/** A `Microsoft.Owin.*String` class. */

View File

@@ -5,11 +5,6 @@
import csharp
private import semmle.code.csharp.frameworks.system.security.cryptography.SymmetricAlgorithm
/** Array of type Byte */
deprecated class ByteArray extends ArrayType {
ByteArray() { this.getElementType() instanceof ByteType }
}
/** Abstract class for all sources of keys */
abstract class KeySource extends DataFlow::Node { }

View File

@@ -5,6 +5,7 @@
import csharp
private import semmle.code.csharp.security.dataflow.flowsources.Remote
private import semmle.code.csharp.controlflow.Guards
private import semmle.code.csharp.frameworks.Format
private import semmle.code.csharp.frameworks.system.Web
private import semmle.code.csharp.frameworks.system.web.Mvc
private import semmle.code.csharp.security.Sanitizers
@@ -163,6 +164,36 @@ class ConcatenationSanitizer extends Sanitizer {
}
}
/**
* A string interpolation expression, where the first part (before any inserts) of the
* expression contains the character "?".
*
* This is considered a sanitizer by the same reasoning as `ConcatenationSanitizer`.
*/
private class InterpolationSanitizer extends Sanitizer {
InterpolationSanitizer() {
this.getExpr().(InterpolatedStringExpr).getText(0).getValue().matches("%?%")
}
}
/**
* A call to `string.Format`, where the format expression (before any inserts)
* contains the character "?".
*
* This is considered a sanitizer by the same reasoning as `ConcatenationSanitizer`.
*/
private class StringFormatSanitizer extends Sanitizer {
StringFormatSanitizer() {
exists(FormatCall c, Expr e, int index, string format |
c = this.getExpr() and e = c.getFormatExpr()
|
format = e.(StringLiteral).getValue() and
exists(format.regexpFind("\\{[0-9]+\\}", 0, index)) and
format.substring(0, index).matches("%?%")
)
}
}
/** A call to an URL encoder. */
class UrlEncodeSanitizer extends Sanitizer {
UrlEncodeSanitizer() { this.getExpr() instanceof UrlSanitizedExpr }

View File

@@ -0,0 +1,31 @@
/** Provides classes representing various flow sources for taint tracking. */
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import codeql.threatmodels.ThreatModels
import semmle.code.csharp.security.dataflow.flowsources.Remote
import semmle.code.csharp.security.dataflow.flowsources.Local
import semmle.code.csharp.security.dataflow.flowsources.Stored
/**
* A data flow source.
*/
abstract class SourceNode extends DataFlow::Node {
/**
* Gets a string that represents the source kind with respect to threat modeling.
*/
abstract string getThreatModel();
}
/**
* A class of data flow sources that respects the
* current threat model configuration.
*/
class ThreatModelFlowSource extends DataFlow::Node {
ThreatModelFlowSource() {
exists(string kind |
// Specific threat model.
currentThreatModel(kind) and
(this.(SourceNode).getThreatModel() = kind or sourceNode(this, kind))
)
}
}

View File

@@ -5,11 +5,14 @@
import csharp
private import semmle.code.csharp.frameworks.system.windows.Forms
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.security.dataflow.flowsources.FlowSources
/** A data flow source of local data. */
abstract class LocalFlowSource extends DataFlow::Node {
abstract class LocalFlowSource extends SourceNode {
/** Gets a string that describes the type of this local flow source. */
abstract string getSourceType();
override string getThreatModel() { result = "local" }
}
private class ExternalLocalFlowSource extends LocalFlowSource {

View File

@@ -13,11 +13,14 @@ private import semmle.code.csharp.frameworks.WCF
private import semmle.code.csharp.frameworks.microsoft.Owin
private import semmle.code.csharp.frameworks.microsoft.AspNetCore
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.security.dataflow.flowsources.FlowSources
/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends DataFlow::Node {
abstract class RemoteFlowSource extends SourceNode {
/** Gets a string that describes the type of this remote flow source. */
abstract string getSourceType();
override string getThreatModel() { result = "remote" }
}
/**

View File

@@ -9,15 +9,25 @@ private import semmle.code.csharp.frameworks.system.data.Entity
private import semmle.code.csharp.frameworks.EntityFramework
private import semmle.code.csharp.frameworks.NHibernate
private import semmle.code.csharp.frameworks.Sql
private import semmle.code.csharp.security.dataflow.flowsources.FlowSources
/** A data flow source of stored user input. */
abstract class StoredFlowSource extends DataFlow::Node { }
abstract class StoredFlowSource extends SourceNode {
override string getThreatModel() { result = "local" }
}
/**
* A node with input from a database.
*/
abstract class DatabaseInputSource extends StoredFlowSource {
override string getThreatModel() { result = "database" }
}
/**
* An expression that has a type of `DbRawSqlQuery`, representing the result of an Entity Framework
* SqlQuery.
*/
class DbRawSqlStoredFlowSource extends StoredFlowSource {
class DbRawSqlStoredFlowSource extends DatabaseInputSource {
DbRawSqlStoredFlowSource() {
this.asExpr().getType() instanceof SystemDataEntityInfrastructure::DbRawSqlQuery
}
@@ -27,14 +37,14 @@ class DbRawSqlStoredFlowSource extends StoredFlowSource {
* An expression that has a type of `DbDataReader` or a sub-class, representing the result of a
* data command.
*/
class DbDataReaderStoredFlowSource extends StoredFlowSource {
class DbDataReaderStoredFlowSource extends DatabaseInputSource {
DbDataReaderStoredFlowSource() {
this.asExpr().getType() = any(SystemDataCommon::DbDataReader dataReader).getASubType*()
}
}
/** An expression that accesses a method of `DbDataReader` or a sub-class. */
class DbDataReaderMethodStoredFlowSource extends StoredFlowSource {
class DbDataReaderMethodStoredFlowSource extends DatabaseInputSource {
DbDataReaderMethodStoredFlowSource() {
this.asExpr().(MethodCall).getTarget().getDeclaringType() =
any(SystemDataCommon::DbDataReader dataReader).getASubType*()
@@ -42,15 +52,19 @@ class DbDataReaderMethodStoredFlowSource extends StoredFlowSource {
}
/** An expression that accesses a property of `DbDataReader` or a sub-class. */
class DbDataReaderPropertyStoredFlowSource extends StoredFlowSource {
class DbDataReaderPropertyStoredFlowSource extends DatabaseInputSource {
DbDataReaderPropertyStoredFlowSource() {
this.asExpr().(PropertyAccess).getTarget().getDeclaringType() =
any(SystemDataCommon::DbDataReader dataReader).getASubType*()
}
}
/** A read of a mapped property. */
class ORMMappedProperty extends StoredFlowSource {
/**
* DEPRECATED: Use `EntityFramework::StoredFlowSource` and `NHibernate::StoredFlowSource` instead.
*
* A read of a mapped property.
*/
deprecated class ORMMappedProperty extends DataFlow::Node {
ORMMappedProperty() {
this instanceof EntityFramework::StoredFlowSource or
this instanceof NHibernate::StoredFlowSource
@@ -60,4 +74,6 @@ class ORMMappedProperty extends StoredFlowSource {
/** A file stream source is considered a stored flow source. */
class FileStreamStoredFlowSource extends StoredFlowSource {
FileStreamStoredFlowSource() { sourceNode(this, "file") }
override string getThreatModel() { result = "file" }
}

View File

@@ -118,15 +118,6 @@ class NamedElement extends Element, @dotnet_named_element {
)
}
/**
* DEPRECATED: Use `hasQualifiedName/2` instead.
* Holds if this element has qualified name `qualifiedName`, for example
* `System.Console.WriteLine`.
*/
deprecated final predicate hasQualifiedName(string qualifiedName) {
qualifiedName = this.getQualifiedName()
}
/**
* DEPRECATED: Use `hasFullyQualifiedName` instead.
*

View File

@@ -220,6 +220,10 @@
<k>@function_pointer_type</k>
<v>47824</v>
</e>
<e>
<k>@inline_array_type</k>
<v>0</v>
</e>
<e>
<k>@uint_ptr_type</k>
<v>0</v>

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added string interpolation expressions and `string.Format` as possible sanitizers for the `cs/web/unvalidated-url-redirection` query.

View File

@@ -17,6 +17,3 @@ module Ssa {
predicate hasUnreachedInstruction(IRFunctionBase irFunc) { none() }
}
/** DEPRECATED: Alias for Ssa */
deprecated module SSA = Ssa;

View File

@@ -2,6 +2,3 @@ import experimental.ir.internal.IRCSharpLanguage as Language
import experimental.ir.implementation.raw.internal.IRConstruction as IRConstruction
import experimental.ir.implementation.unaliased_ssa.internal.SSAConstruction as UnaliasedSsa
import AliasedSSAStub as AliasedSsa
/** DEPRECATED: Alias for AliasedSsa */
deprecated module AliasedSSA = AliasedSsa;

View File

@@ -1068,6 +1068,3 @@ module Ssa {
predicate hasUnreachedInstruction = Cached::hasUnreachedInstructionCached/1;
}
/** DEPRECATED: Alias for Ssa */
deprecated module SSA = Ssa;

View File

@@ -4,13 +4,6 @@ import experimental.ir.implementation.raw.internal.reachability.Dominance as Dom
import experimental.ir.implementation.unaliased_ssa.IR as NewIR
import experimental.ir.implementation.raw.internal.IRConstruction as RawStage
import experimental.ir.implementation.internal.TInstruction::UnaliasedSsaInstructions as SsaInstructions
/** DEPRECATED: Alias for SsaInstructions */
deprecated module SSAInstructions = SsaInstructions;
import experimental.ir.internal.IRCSharpLanguage as Language
import SimpleSSA as Alias
import experimental.ir.implementation.internal.TOperand::UnaliasedSsaOperands as SsaOperands
/** DEPRECATED: Alias for SsaOperands */
deprecated module SSAOperands = SsaOperands;

View File

@@ -0,0 +1,68 @@
using System.Net.Sockets;
using System.Data.SqlClient;
namespace My.Qltest
{
public class Test
{
private TestSources Sources = new TestSources();
private SqlConnection Connection => throw null;
private string BytesToString(byte[] bytes)
{
// Encode bytes to a UTF8 string.
return System.Text.Encoding.UTF8.GetString(bytes);
}
public void M1()
{
// Only a source if "remote" is a selected threat model.
// This is included in the "default" threat model.
using TcpClient client = new TcpClient("localhost", 1234);
using NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
// SQL sink
var command = new SqlCommand("SELECT * FROM Users WHERE Username = '" + BytesToString(buffer) + "'", Connection);
}
public void M2()
{
// Only a source if "database" is a selected threat model.
string result = Sources.ExecuteQuery("SELECT * FROM foo");
// SQL sink
var command = new SqlCommand("SELECT * FROM Users WHERE Username = '" + result + "'", Connection);
}
public void M3()
{
// Only a source if "environment" is a selected threat model.
string result = Sources.ReadEnv("foo");
// SQL sink
var command = new SqlCommand("SELECT * FROM Users WHERE Username = '" + result + "'", Connection);
}
public void M4()
{
// Only a source if "custom" is a selected threat model.
string result = Sources.GetCustom("foo");
// SQL sink
var command = new SqlCommand("SELECT * FROM Users WHERE Username = '" + result + "'", Connection);
}
public void M5()
{
// Only a source if "commandargs" is a selected threat model.
string result = Sources.GetCliArg(0);
// SQL sink
var command = new SqlCommand("SELECT * FROM Users WHERE Username = '" + result + "'", Connection);
}
}
}

View File

@@ -0,0 +1,12 @@
private import csharp
private import semmle.code.csharp.dataflow.DataFlow
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.security.dataflow.flowsources.FlowSources
private module ThreatModelConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
predicate isSink(DataFlow::Node sink) { sinkNode(sink, _) }
}
module ThreatModel = TaintTracking::Global<ThreatModelConfig>;

View File

@@ -0,0 +1,26 @@
namespace My.Qltest
{
public class TestSources
{
public string ExecuteQuery(string query)
{
return null;
}
public string ReadEnv(string env)
{
return null;
}
public string GetCustom(string s)
{
return null;
}
public string GetCliArg(int i)
{
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/System.Data.SqlClient/4.8.5/System.Data.SqlClient.csproj

View File

@@ -0,0 +1,23 @@
edges
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:25:29:25:34 | access to local variable stream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:85:28:105 | call to method BytesToString : String | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:28:85:28:105 | call to method BytesToString : String |
nodes
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | semmle.label | bytes : Byte[] [element] : Object |
| Test.cs:15:20:15:61 | call to method GetString : String | semmle.label | call to method GetString : String |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | semmle.label | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | semmle.label | call to method GetStream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | semmle.label | access to local variable stream : NetworkStream |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | semmle.label | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:42:28:111 | ... + ... | semmle.label | ... + ... |
| Test.cs:28:85:28:105 | call to method BytesToString : String | semmle.label | call to method BytesToString : String |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | semmle.label | access to local variable buffer : Byte[] [element] : Object |
subpaths
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String | Test.cs:28:85:28:105 | call to method BytesToString : String |
#select
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:28:42:28:111 | ... + ... |

View File

@@ -0,0 +1,15 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data: []
- addsTo:
pack: codeql/csharp-all
extensible: sourceModel
data:
- ["My.Qltest", "TestSources", False, "ExecuteQuery", "(System.String)", "", "ReturnValue", "database", "manual"]
- ["My.Qltest", "TestSources", False, "ReadEnv", "(System.String)", "", "ReturnValue", "environment", "manual"]
- ["My.Qltest", "TestSources", False, "GetCustom", "(System.String)", "", "ReturnValue", "custom", "manual"]
- ["My.Qltest", "TestSources", False, "GetCliArg", "(System.Int32)", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,10 @@
/**
* This is a dataflow test using the "default" threat model.
*/
import Test
import ThreatModel::PathGraph
from ThreatModel::PathNode source, ThreatModel::PathNode sink
where ThreatModel::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,27 @@
edges
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:25:29:25:34 | access to local variable stream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:85:28:105 | call to method BytesToString : String | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:28:85:28:105 | call to method BytesToString : String |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
nodes
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | semmle.label | bytes : Byte[] [element] : Object |
| Test.cs:15:20:15:61 | call to method GetString : String | semmle.label | call to method GetString : String |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | semmle.label | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | semmle.label | call to method GetStream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | semmle.label | access to local variable stream : NetworkStream |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | semmle.label | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:42:28:111 | ... + ... | semmle.label | ... + ... |
| Test.cs:28:85:28:105 | call to method BytesToString : String | semmle.label | call to method BytesToString : String |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | semmle.label | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | semmle.label | call to method ExecuteQuery : String |
| Test.cs:37:42:37:96 | ... + ... | semmle.label | ... + ... |
subpaths
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String | Test.cs:28:85:28:105 | call to method BytesToString : String |
#select
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |

View File

@@ -0,0 +1,16 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["database", true, 0]
- addsTo:
pack: codeql/csharp-all
extensible: sourceModel
data:
- ["My.Qltest", "TestSources", False, "ExecuteQuery", "(System.String)", "", "ReturnValue", "database", "manual"]
- ["My.Qltest", "TestSources", False, "ReadEnv", "(System.String)", "", "ReturnValue", "environment", "manual"]
- ["My.Qltest", "TestSources", False, "GetCustom", "(System.String)", "", "ReturnValue", "custom", "manual"]
- ["My.Qltest", "TestSources", False, "GetCliArg", "(System.Int32)", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,11 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of "database".
*/
import Test
import ThreatModel::PathGraph
from ThreatModel::PathNode source, ThreatModel::PathNode sink
where ThreatModel::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,35 @@
edges
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:25:29:25:34 | access to local variable stream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:85:28:105 | call to method BytesToString : String | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:28:85:28:105 | call to method BytesToString : String |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | Test.cs:46:42:46:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |
nodes
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | semmle.label | bytes : Byte[] [element] : Object |
| Test.cs:15:20:15:61 | call to method GetString : String | semmle.label | call to method GetString : String |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | semmle.label | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | semmle.label | call to method GetStream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | semmle.label | access to local variable stream : NetworkStream |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | semmle.label | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:42:28:111 | ... + ... | semmle.label | ... + ... |
| Test.cs:28:85:28:105 | call to method BytesToString : String | semmle.label | call to method BytesToString : String |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | semmle.label | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | semmle.label | call to method ExecuteQuery : String |
| Test.cs:37:42:37:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | semmle.label | call to method ReadEnv : String |
| Test.cs:46:42:46:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | semmle.label | call to method GetCliArg : String |
| Test.cs:65:42:65:96 | ... + ... | semmle.label | ... + ... |
subpaths
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String | Test.cs:28:85:28:105 | call to method BytesToString : String |
#select
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | Test.cs:46:42:46:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |

View File

@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["local", true, 0]
- addsTo:
pack: codeql/csharp-all
extensible: sourceModel
data:
- ["My.Qltest", "TestSources", False, "ExecuteQuery", "(System.String)", "", "ReturnValue", "database", "manual"]
- ["My.Qltest", "TestSources", False, "ReadEnv", "(System.String)", "", "ReturnValue", "environment", "manual"]
- ["My.Qltest", "TestSources", False, "GetCustom", "(System.String)", "", "ReturnValue", "custom", "manual"]
- ["My.Qltest", "TestSources", False, "GetCliArg", "(System.Int32)", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,11 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of the threat model group "local".
*/
import Test
import ThreatModel::PathGraph
from ThreatModel::PathNode source, ThreatModel::PathNode sink
where ThreatModel::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,39 @@
edges
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:25:29:25:34 | access to local variable stream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:85:28:105 | call to method BytesToString : String | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:28:85:28:105 | call to method BytesToString : String |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | Test.cs:46:42:46:96 | ... + ... |
| Test.cs:53:29:53:52 | call to method GetCustom : String | Test.cs:56:42:56:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |
nodes
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | semmle.label | bytes : Byte[] [element] : Object |
| Test.cs:15:20:15:61 | call to method GetString : String | semmle.label | call to method GetString : String |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | semmle.label | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | semmle.label | call to method GetStream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | semmle.label | access to local variable stream : NetworkStream |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | semmle.label | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:42:28:111 | ... + ... | semmle.label | ... + ... |
| Test.cs:28:85:28:105 | call to method BytesToString : String | semmle.label | call to method BytesToString : String |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | semmle.label | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | semmle.label | call to method ExecuteQuery : String |
| Test.cs:37:42:37:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | semmle.label | call to method ReadEnv : String |
| Test.cs:46:42:46:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:53:29:53:52 | call to method GetCustom : String | semmle.label | call to method GetCustom : String |
| Test.cs:56:42:56:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | semmle.label | call to method GetCliArg : String |
| Test.cs:65:42:65:96 | ... + ... | semmle.label | ... + ... |
subpaths
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String | Test.cs:28:85:28:105 | call to method BytesToString : String |
#select
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | Test.cs:46:42:46:96 | ... + ... |
| Test.cs:53:29:53:52 | call to method GetCustom : String | Test.cs:56:42:56:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |

View File

@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["all", true, 0]
- addsTo:
pack: codeql/csharp-all
extensible: sourceModel
data:
- ["My.Qltest", "TestSources", False, "ExecuteQuery", "(System.String)", "", "ReturnValue", "database", "manual"]
- ["My.Qltest", "TestSources", False, "ReadEnv", "(System.String)", "", "ReturnValue", "environment", "manual"]
- ["My.Qltest", "TestSources", False, "GetCustom", "(System.String)", "", "ReturnValue", "custom", "manual"]
- ["My.Qltest", "TestSources", False, "GetCliArg", "(System.Int32)", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,10 @@
/**
* This is a dataflow test using "all" threat models.
*/
import Test
import ThreatModel::PathGraph
from ThreatModel::PathNode source, ThreatModel::PathNode sink
where ThreatModel::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,31 @@
edges
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:25:29:25:34 | access to local variable stream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:85:28:105 | call to method BytesToString : String | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:28:85:28:105 | call to method BytesToString : String |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | Test.cs:46:42:46:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |
nodes
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | semmle.label | bytes : Byte[] [element] : Object |
| Test.cs:15:20:15:61 | call to method GetString : String | semmle.label | call to method GetString : String |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | semmle.label | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | semmle.label | call to method GetStream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | semmle.label | access to local variable stream : NetworkStream |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | semmle.label | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:42:28:111 | ... + ... | semmle.label | ... + ... |
| Test.cs:28:85:28:105 | call to method BytesToString : String | semmle.label | call to method BytesToString : String |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | semmle.label | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | semmle.label | call to method ReadEnv : String |
| Test.cs:46:42:46:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | semmle.label | call to method GetCliArg : String |
| Test.cs:65:42:65:96 | ... + ... | semmle.label | ... + ... |
subpaths
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String | Test.cs:28:85:28:105 | call to method BytesToString : String |
#select
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:43:29:43:50 | call to method ReadEnv : String | Test.cs:46:42:46:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |

View File

@@ -0,0 +1,18 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["environment", true, 0]
- ["commandargs", true, 0]
- addsTo:
pack: codeql/csharp-all
extensible: sourceModel
data:
- ["My.Qltest", "TestSources", False, "ExecuteQuery", "(System.String)", "", "ReturnValue", "database", "manual"]
- ["My.Qltest", "TestSources", False, "ReadEnv", "(System.String)", "", "ReturnValue", "environment", "manual"]
- ["My.Qltest", "TestSources", False, "GetCustom", "(System.String)", "", "ReturnValue", "custom", "manual"]
- ["My.Qltest", "TestSources", False, "GetCliArg", "(System.Int32)", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,11 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of "environment" and "commandargs".
*/
import Test
import ThreatModel::PathGraph
from ThreatModel::PathNode source, ThreatModel::PathNode sink
where ThreatModel::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,31 @@
edges
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:25:29:25:34 | access to local variable stream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:85:28:105 | call to method BytesToString : String | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:28:85:28:105 | call to method BytesToString : String |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |
nodes
| Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | semmle.label | bytes : Byte[] [element] : Object |
| Test.cs:15:20:15:61 | call to method GetString : String | semmle.label | call to method GetString : String |
| Test.cs:15:56:15:60 | access to parameter bytes : Byte[] [element] : Object | semmle.label | access to parameter bytes : Byte[] [element] : Object |
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | semmle.label | call to method GetStream : NetworkStream |
| Test.cs:25:29:25:34 | access to local variable stream : NetworkStream | semmle.label | access to local variable stream : NetworkStream |
| Test.cs:25:41:25:46 | [post] access to local variable buffer : Byte[] [element] : Object | semmle.label | [post] access to local variable buffer : Byte[] [element] : Object |
| Test.cs:28:42:28:111 | ... + ... | semmle.label | ... + ... |
| Test.cs:28:85:28:105 | call to method BytesToString : String | semmle.label | call to method BytesToString : String |
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | semmle.label | access to local variable buffer : Byte[] [element] : Object |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | semmle.label | call to method ExecuteQuery : String |
| Test.cs:37:42:37:96 | ... + ... | semmle.label | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | semmle.label | call to method GetCliArg : String |
| Test.cs:65:42:65:96 | ... + ... | semmle.label | ... + ... |
subpaths
| Test.cs:28:99:28:104 | access to local variable buffer : Byte[] [element] : Object | Test.cs:12:45:12:49 | bytes : Byte[] [element] : Object | Test.cs:15:20:15:61 | call to method GetString : String | Test.cs:28:85:28:105 | call to method BytesToString : String |
#select
| Test.cs:23:42:23:59 | call to method GetStream : NetworkStream | Test.cs:28:42:28:111 | ... + ... |
| Test.cs:34:29:34:69 | call to method ExecuteQuery : String | Test.cs:37:42:37:96 | ... + ... |
| Test.cs:62:29:62:48 | call to method GetCliArg : String | Test.cs:65:42:65:96 | ... + ... |

View File

@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["local", true, 0]
- ["environment", false, 1]
- addsTo:
pack: codeql/csharp-all
extensible: sourceModel
data:
- ["My.Qltest", "TestSources", False, "ExecuteQuery", "(System.String)", "", "ReturnValue", "database", "manual"]
- ["My.Qltest", "TestSources", False, "ReadEnv", "(System.String)", "", "ReturnValue", "environment", "manual"]
- ["My.Qltest", "TestSources", False, "GetCustom", "(System.String)", "", "ReturnValue", "custom", "manual"]
- ["My.Qltest", "TestSources", False, "GetCliArg", "(System.Int32)", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,12 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of the threat model group "local", but without the
* "environment" threat model.
*/
import Test
import ThreatModel::PathGraph
from ThreatModel::PathNode source, ThreatModel::PathNode sink
where ThreatModel::flowPath(source, sink)
select source, sink

View File

@@ -41,7 +41,7 @@ public class UrlRedirectHandler : IHttpHandler
// GOOD: Redirecting to the RawUrl only reloads the current Url
ctx.Response.Redirect(ctx.Request.RawUrl);
// GOOD: The attacker can only control the parameters, not the locaiton
// GOOD: The attacker can only control the parameters, not the location
ctx.Response.Redirect("foo.asp?param=" + url);
// BAD: Using Transfer with unvalidated user input
@@ -56,6 +56,24 @@ public class UrlRedirectHandler : IHttpHandler
{
ctx.Response.Redirect(url3);
}
// GOOD: The attacker can only control the parameters, not the location
ctx.Response.Redirect($"foo.asp?param={url}");
// BAD: The attacker can control the location
ctx.Response.Redirect($"{url}.asp?param=foo");
// GOOD: The attacker can only control the parameters, not the location
ctx.Response.Redirect(string.Format("foo.asp?param={0}", url));
// BAD: The attacker can control the location
ctx.Response.Redirect(string.Format("{0}.asp?param=foo", url));
// GOOD: The attacker can only control the parameters, not the location
ctx.Response.Redirect(string.Format("foo.asp?{1}param={0}", url, url));
// BAD: The attacker can control the location
ctx.Response.Redirect(string.Format("{1}.asp?{0}param=foo", url, url));
}
// Implementation as recommended by Microsoft.

View File

@@ -2,9 +2,20 @@ edges
| UrlRedirect.cs:13:31:13:53 | access to property QueryString : NameValueCollection | UrlRedirect.cs:13:31:13:61 | access to indexer |
| UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:23:22:23:52 | access to indexer : String |
| UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:48:29:48:31 | access to local variable url |
| UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:64:31:64:52 | $"..." |
| UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:70:66:70:68 | access to local variable url : String |
| UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:76:69:76:71 | access to local variable url : String |
| UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:76:74:76:76 | access to local variable url : String |
| UrlRedirect.cs:23:22:23:52 | access to indexer : String | UrlRedirect.cs:48:29:48:31 | access to local variable url |
| UrlRedirect.cs:23:22:23:52 | access to indexer : String | UrlRedirect.cs:64:31:64:52 | $"..." |
| UrlRedirect.cs:23:22:23:52 | access to indexer : String | UrlRedirect.cs:70:66:70:68 | access to local variable url : String |
| UrlRedirect.cs:23:22:23:52 | access to indexer : String | UrlRedirect.cs:76:69:76:71 | access to local variable url : String |
| UrlRedirect.cs:23:22:23:52 | access to indexer : String | UrlRedirect.cs:76:74:76:76 | access to local variable url : String |
| UrlRedirect.cs:38:44:38:66 | access to property QueryString : NameValueCollection | UrlRedirect.cs:38:44:38:74 | access to indexer |
| UrlRedirect.cs:39:47:39:69 | access to property QueryString : NameValueCollection | UrlRedirect.cs:39:47:39:77 | access to indexer |
| UrlRedirect.cs:70:66:70:68 | access to local variable url : String | UrlRedirect.cs:70:31:70:69 | call to method Format |
| UrlRedirect.cs:76:69:76:71 | access to local variable url : String | UrlRedirect.cs:76:31:76:77 | call to method Format |
| UrlRedirect.cs:76:74:76:76 | access to local variable url : String | UrlRedirect.cs:76:31:76:77 | call to method Format |
| UrlRedirectCore.cs:13:44:13:48 | value : String | UrlRedirectCore.cs:16:22:16:26 | access to parameter value |
| UrlRedirectCore.cs:13:44:13:48 | value : String | UrlRedirectCore.cs:19:44:19:48 | call to operator implicit conversion |
| UrlRedirectCore.cs:13:44:13:48 | value : String | UrlRedirectCore.cs:25:46:25:50 | call to operator implicit conversion |
@@ -26,6 +37,12 @@ nodes
| UrlRedirect.cs:39:47:39:69 | access to property QueryString : NameValueCollection | semmle.label | access to property QueryString : NameValueCollection |
| UrlRedirect.cs:39:47:39:77 | access to indexer | semmle.label | access to indexer |
| UrlRedirect.cs:48:29:48:31 | access to local variable url | semmle.label | access to local variable url |
| UrlRedirect.cs:64:31:64:52 | $"..." | semmle.label | $"..." |
| UrlRedirect.cs:70:31:70:69 | call to method Format | semmle.label | call to method Format |
| UrlRedirect.cs:70:66:70:68 | access to local variable url : String | semmle.label | access to local variable url : String |
| UrlRedirect.cs:76:31:76:77 | call to method Format | semmle.label | call to method Format |
| UrlRedirect.cs:76:69:76:71 | access to local variable url : String | semmle.label | access to local variable url : String |
| UrlRedirect.cs:76:74:76:76 | access to local variable url : String | semmle.label | access to local variable url : String |
| UrlRedirectCore.cs:13:44:13:48 | value : String | semmle.label | value : String |
| UrlRedirectCore.cs:16:22:16:26 | access to parameter value | semmle.label | access to parameter value |
| UrlRedirectCore.cs:19:44:19:48 | call to operator implicit conversion | semmle.label | call to operator implicit conversion |
@@ -45,6 +62,9 @@ subpaths
| UrlRedirect.cs:38:44:38:74 | access to indexer | UrlRedirect.cs:38:44:38:66 | access to property QueryString : NameValueCollection | UrlRedirect.cs:38:44:38:74 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:38:44:38:66 | access to property QueryString | user-provided value |
| UrlRedirect.cs:39:47:39:77 | access to indexer | UrlRedirect.cs:39:47:39:69 | access to property QueryString : NameValueCollection | UrlRedirect.cs:39:47:39:77 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:39:47:39:69 | access to property QueryString | user-provided value |
| UrlRedirect.cs:48:29:48:31 | access to local variable url | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:48:29:48:31 | access to local variable url | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value |
| UrlRedirect.cs:64:31:64:52 | $"..." | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:64:31:64:52 | $"..." | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value |
| UrlRedirect.cs:70:31:70:69 | call to method Format | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:70:31:70:69 | call to method Format | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value |
| UrlRedirect.cs:76:31:76:77 | call to method Format | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:76:31:76:77 | call to method Format | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value |
| UrlRedirectCore.cs:16:22:16:26 | access to parameter value | UrlRedirectCore.cs:13:44:13:48 | value : String | UrlRedirectCore.cs:16:22:16:26 | access to parameter value | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:13:44:13:48 | value | user-provided value |
| UrlRedirectCore.cs:19:44:19:48 | call to operator implicit conversion | UrlRedirectCore.cs:13:44:13:48 | value : String | UrlRedirectCore.cs:19:44:19:48 | call to operator implicit conversion | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:13:44:13:48 | value | user-provided value |
| UrlRedirectCore.cs:25:46:25:50 | call to operator implicit conversion | UrlRedirectCore.cs:13:44:13:48 | value : String | UrlRedirectCore.cs:25:46:25:50 | call to operator implicit conversion | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:13:44:13:48 | value | user-provided value |

View File

@@ -51,6 +51,7 @@ and the CodeQL library pack ``codeql/go-all`` (`changelog <https://github.com/gi
:widths: auto
Name, Category
`AWS Lambda <https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html>`_, Serverless framework
`beego <https://beego.me/>`_, Web/logging/database framework
`Chi <https://github.com/go-chi/chi>`_, Web framework
Couchbase (`gocb <https://github.com/couchbase/gocb>`_ and `go-couchbase <http://www.github.com/couchbase/go-couchbase>`_), Database
@@ -144,6 +145,7 @@ and the CodeQL library pack ``codeql/javascript-all`` (`changelog <https://githu
Name, Category
angular (modern version), HTML framework
angular.js (legacy version), HTML framework
AWS Lambda, Serverless framework
axios, Network communicator
browser, Runtime environment
EJS, templating language
@@ -191,7 +193,7 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.co
:widths: auto
Name, Category
AWS Lambda, Web framework
AWS Lambda, Serverless framework
aiohttp.web, Web framework
Django, Web framework
djangorestframework, Web framework

View File

@@ -0,0 +1,283 @@
package autobuilder
import (
"fmt"
"log"
"os"
"github.com/github/codeql-go/extractor/diagnostics"
"github.com/github/codeql-go/extractor/project"
"github.com/github/codeql-go/extractor/toolchain"
"golang.org/x/mod/semver"
)
const minGoVersion = "1.11"
const maxGoVersion = "1.21"
type versionInfo struct {
goModVersion string // The version of Go found in the go directive in the `go.mod` file.
goModVersionFound bool // Whether a `go` directive was found in the `go.mod` file.
goEnvVersion string // The version of Go found in the environment.
goEnvVersionFound bool // Whether an installation of Go was found in the environment.
}
func (v versionInfo) String() string {
return fmt.Sprintf(
"go.mod version: %s, go.mod directive found: %t, go env version: %s, go installation found: %t",
v.goModVersion, v.goModVersionFound, v.goEnvVersion, v.goEnvVersionFound)
}
// Check if `version` is lower than `minGoVersion`. Note that for this comparison we ignore the
// patch part of the version, so 1.20.1 and 1.20 are considered equal.
func belowSupportedRange(version string) bool {
return semver.Compare(semver.MajorMinor("v"+version), "v"+minGoVersion) < 0
}
// Check if `version` is higher than `maxGoVersion`. Note that for this comparison we ignore the
// patch part of the version, so 1.20.1 and 1.20 are considered equal.
func aboveSupportedRange(version string) bool {
return semver.Compare(semver.MajorMinor("v"+version), "v"+maxGoVersion) > 0
}
// Check if `version` is lower than `minGoVersion` or higher than `maxGoVersion`. Note that for
// this comparison we ignore the patch part of the version, so 1.20.1 and 1.20 are considered
// equal.
func outsideSupportedRange(version string) bool {
return belowSupportedRange(version) || aboveSupportedRange(version)
}
// Assuming `v.goModVersionFound` is false, emit a diagnostic and return the version to install,
// or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionNotFound(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// There is no Go version installed in the environment. We have no indication which version
// was intended to be used to build this project. Go versions are generally backwards
// compatible, so we install the maximum supported version.
msg = "No version of Go installed and no `go.mod` file found. Requesting the maximum " +
"supported version of Go (" + maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitNoGoModAndNoGoEnv(msg)
} else if outsideSupportedRange(v.goEnvVersion) {
// The Go version installed in the environment is not supported. We have no indication
// which version was intended to be used to build this project. Go versions are generally
// backwards compatible, so we install the maximum supported version.
msg = "No `go.mod` file found. The version of Go installed in the environment (" +
v.goEnvVersion + ") is outside of the supported range (" + minGoVersion + "-" +
maxGoVersion + "). Requesting the maximum supported version of Go (" + maxGoVersion +
")."
version = maxGoVersion
diagnostics.EmitNoGoModAndGoEnvUnsupported(msg)
} else {
// The version of Go that is installed is supported. We have no indication which version
// was intended to be used to build this project. We assume that the installed version is
// suitable and do not install a version of Go.
msg = "No `go.mod` file found. Version " + v.goEnvVersion + " installed in the " +
"environment is supported. Not requesting any version of Go."
version = ""
diagnostics.EmitNoGoModAndGoEnvSupported(msg)
}
return msg, version
}
// Assuming `v.goModVersion` is above the supported range, emit a diagnostic and return the
// version to install, or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionTooHigh(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// The version in the `go.mod` file is above the supported range. There is no Go version
// installed. We install the maximum supported version as a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). No version of Go installed. Requesting the maximum supported version of Go (" +
maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitGoModVersionTooHighAndNoGoEnv(msg)
} else if aboveSupportedRange(v.goEnvVersion) {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is above the supported range. We do not install a version of Go.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooHigh(msg)
} else if belowSupportedRange(v.goEnvVersion) {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is below the supported range. We install the maximum supported version as
// a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
"). Requesting the maximum supported version of Go (" + maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooLow(msg)
} else if semver.Compare("v"+maxGoVersion, "v"+v.goEnvVersion) > 0 {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is supported and below the maximum supported version. We install the
// maximum supported version as a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is below the maximum supported version (" + maxGoVersion +
"). Requesting the maximum supported version of Go (" + maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitGoModVersionTooHighAndEnvVersionBelowMax(msg)
} else {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is the maximum supported version. We do not install a version of Go.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is the maximum supported version (" + maxGoVersion +
"). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionTooHighAndEnvVersionMax(msg)
}
return msg, version
}
// Assuming `v.goModVersion` is below the supported range, emit a diagnostic and return the
// version to install, or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionTooLow(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// There is no Go version installed. The version in the `go.mod` file is below the
// supported range. Go versions are generally backwards compatible, so we install the
// minimum supported version.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
"). No version of Go installed. Requesting the minimum supported version of Go (" +
minGoVersion + ")."
version = minGoVersion
diagnostics.EmitGoModVersionTooLowAndNoGoEnv(msg)
} else if outsideSupportedRange(v.goEnvVersion) {
// The version of Go that is installed is outside of the supported range. The version
// in the `go.mod` file is below the supported range. Go versions are generally
// backwards compatible, so we install the minimum supported version.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is outside of the supported range (" + minGoVersion + "-" + maxGoVersion + "). " +
"Requesting the minimum supported version of Go (" + minGoVersion + ")."
version = minGoVersion
diagnostics.EmitGoModVersionTooLowAndEnvVersionUnsupported(msg)
} else {
// The version of Go that is installed is supported. The version in the `go.mod` file is
// below the supported range. We do not install a version of Go.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is supported and is high enough for the version found in the `go.mod` file (" +
v.goModVersion + "). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionTooLowAndEnvVersionSupported(msg)
}
return msg, version
}
// Assuming `v.goModVersion` is in the supported range, emit a diagnostic and return the version
// to install, or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionSupported(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// There is no Go version installed. The version in the `go.mod` file is supported.
// We install the version from the `go.mod` file.
msg = "No version of Go installed. Requesting the version of Go found in the `go.mod` " +
"file (" + v.goModVersion + ")."
version = v.goModVersion
diagnostics.EmitGoModVersionSupportedAndNoGoEnv(msg)
} else if outsideSupportedRange(v.goEnvVersion) {
// The version of Go that is installed is outside of the supported range. The version in
// the `go.mod` file is supported. We install the version from the `go.mod` file.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is outside of the supported range (" + minGoVersion + "-" + maxGoVersion + "). " +
"Requesting the version of Go from the `go.mod` file (" +
v.goModVersion + ")."
version = v.goModVersion
diagnostics.EmitGoModVersionSupportedAndGoEnvUnsupported(msg)
} else if semver.Compare("v"+v.goModVersion, "v"+v.goEnvVersion) > 0 {
// The version of Go that is installed is supported. The version in the `go.mod` file is
// supported and is higher than the version that is installed. We install the version from
// the `go.mod` file.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is lower than the version found in the `go.mod` file (" + v.goModVersion +
"). Requesting the version of Go from the `go.mod` file (" + v.goModVersion + ")."
version = v.goModVersion
diagnostics.EmitGoModVersionSupportedHigherGoEnv(msg)
} else {
// The version of Go that is installed is supported. The version in the `go.mod` file is
// supported and is lower than or equal to the version that is installed. We do not install
// a version of Go.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is supported and is high enough for the version found in the `go.mod` file (" +
v.goModVersion + "). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionSupportedLowerEqualGoEnv(msg)
}
return msg, version
}
// Check the versions of Go found in the environment and in the `go.mod` file, and return a
// version to install. If the version is the empty string then no installation is required.
// We never return a version of Go that is outside of the supported range.
//
// +-----------------------+-----------------------+-----------------------+-----------------------------------------------------+------------------------------------------------+
// | Found in go.mod > | *None* | *Below min supported* | *In supported range* | *Above max supported |
// | Installed \/ | | | | |
// |-----------------------|-----------------------|-----------------------|-----------------------------------------------------|------------------------------------------------|
// | *None* | Install max supported | Install min supported | Install version from go.mod | Install max supported |
// | *Below min supported* | Install max supported | Install min supported | Install version from go.mod | Install max supported |
// | *In supported range* | No action | No action | Install version from go.mod if newer than installed | Install max supported if newer than installed |
// | *Above max supported* | Install max supported | Install min supported | Install version from go.mod | No action |
// +-----------------------+-----------------------+-----------------------+-----------------------------------------------------+------------------------------------------------+
func getVersionToInstall(v versionInfo) (msg, version string) {
if !v.goModVersionFound {
return getVersionWhenGoModVersionNotFound(v)
}
if aboveSupportedRange(v.goModVersion) {
return getVersionWhenGoModVersionTooHigh(v)
}
if belowSupportedRange(v.goModVersion) {
return getVersionWhenGoModVersionTooLow(v)
}
return getVersionWhenGoModVersionSupported(v)
}
// Output some JSON to stdout specifying the version of Go to install, unless `version` is the
// empty string.
func outputEnvironmentJson(version string) {
var content string
if version == "" {
content = `{ "go": {} }`
} else {
content = `{ "go": { "version": "` + version + `" } }`
}
_, err := fmt.Fprint(os.Stdout, content)
if err != nil {
log.Println("Failed to write environment json to stdout: ")
log.Println(err)
}
}
// Get the version of Go to install and output it to stdout as json.
func IdentifyEnvironment() {
var v versionInfo
buildInfo := project.GetBuildInfo(false)
goVersionInfo := project.TryReadGoDirective(buildInfo)
v.goModVersion, v.goModVersionFound = goVersionInfo.Version, goVersionInfo.Found
v.goEnvVersionFound = toolchain.IsInstalled()
if v.goEnvVersionFound {
v.goEnvVersion = toolchain.GetEnvGoVersion()[2:]
}
msg, versionToInstall := getVersionToInstall(v)
log.Println(msg)
outputEnvironmentJson(versionToInstall)
}

View File

@@ -0,0 +1,48 @@
package autobuilder
import "testing"
func TestGetVersionToInstall(t *testing.T) {
tests := map[versionInfo]string{
// getVersionWhenGoModVersionNotFound()
{"", false, "", false}: maxGoVersion,
{"", false, "1.2.2", true}: maxGoVersion,
{"", false, "9999.0.1", true}: maxGoVersion,
{"", false, "1.11.13", true}: "",
{"", false, "1.20.3", true}: "",
// getVersionWhenGoModVersionTooHigh()
{"9999.0", true, "", false}: maxGoVersion,
{"9999.0", true, "9999.0.1", true}: "",
{"9999.0", true, "1.1", true}: maxGoVersion,
{"9999.0", true, minGoVersion, false}: maxGoVersion,
{"9999.0", true, maxGoVersion, true}: "",
// getVersionWhenGoModVersionTooLow()
{"0.0", true, "", false}: minGoVersion,
{"0.0", true, "9999.0", true}: minGoVersion,
{"0.0", true, "1.2.2", true}: minGoVersion,
{"0.0", true, "1.20.3", true}: "",
// getVersionWhenGoModVersionSupported()
{"1.20", true, "", false}: "1.20",
{"1.11", true, "", false}: "1.11",
{"1.20", true, "1.2.2", true}: "1.20",
{"1.11", true, "1.2.2", true}: "1.11",
{"1.20", true, "9999.0.1", true}: "1.20",
{"1.11", true, "9999.0.1", true}: "1.11",
// go.mod version > go installation version
{"1.20", true, "1.11.13", true}: "1.20",
{"1.20", true, "1.12", true}: "1.20",
// go.mod version <= go installation version (Note comparisons ignore the patch version)
{"1.11", true, "1.20", true}: "",
{"1.11", true, "1.20.3", true}: "",
{"1.20", true, "1.20.3", true}: "",
}
for input, expected := range tests {
_, actual := getVersionToInstall(input)
if actual != expected {
t.Errorf("Expected getVersionToInstall(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual)
}
}
}

View File

@@ -1,7 +1,6 @@
package main
import (
"bufio"
"fmt"
"log"
"net/url"
@@ -10,13 +9,14 @@ import (
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"golang.org/x/mod/semver"
"github.com/github/codeql-go/extractor/autobuilder"
"github.com/github/codeql-go/extractor/diagnostics"
"github.com/github/codeql-go/extractor/project"
"github.com/github/codeql-go/extractor/toolchain"
"github.com/github/codeql-go/extractor/util"
)
@@ -56,43 +56,9 @@ Build behavior:
fmt.Fprintf(os.Stderr, "Usage:\n\n %s\n", os.Args[0])
}
var goVersion = ""
// Returns the current Go version as returned by 'go version', e.g. go1.14.4
func getEnvGoVersion() string {
if goVersion == "" {
// Since Go 1.21, running 'go version' in a directory with a 'go.mod' file will attempt to
// download the version of Go specified in there. That may either fail or result in us just
// being told what's already in 'go.mod'. Setting 'GOTOOLCHAIN' to 'local' will force it
// to use the local Go toolchain instead.
cmd := exec.Command("go", "version")
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("Unable to run the go command, is it installed?\nError: %s", err.Error())
}
goVersion = parseGoVersion(string(out))
}
return goVersion
}
// The 'go version' command may output warnings on separate lines before
// the actual version string is printed. This function parses the output
// to retrieve just the version string.
func parseGoVersion(data string) string {
var lastLine string
sc := bufio.NewScanner(strings.NewReader(data))
for sc.Scan() {
lastLine = sc.Text()
}
return strings.Fields(lastLine)[2]
}
// Returns the current Go version in semver format, e.g. v1.14.4
func getEnvGoSemVer() string {
goVersion := getEnvGoVersion()
goVersion := toolchain.GetEnvGoVersion()
if !strings.HasPrefix(goVersion, "go") {
log.Fatalf("Expected 'go version' output of the form 'go1.2.3'; got '%s'", goVersion)
}
@@ -176,59 +142,6 @@ func restoreRepoLayout(fromDir string, dirEntries []string, scratchDirName strin
}
}
// DependencyInstallerMode is an enum describing how dependencies should be installed
type DependencyInstallerMode int
const (
// GoGetNoModules represents dependency installation using `go get` without modules
GoGetNoModules DependencyInstallerMode = iota
// GoGetWithModules represents dependency installation using `go get` with modules
GoGetWithModules
// Dep represent dependency installation using `dep ensure`
Dep
// Glide represents dependency installation using `glide install`
Glide
)
// ModMode corresponds to the possible values of the -mod flag for the Go compiler
type ModMode int
const (
ModUnset ModMode = iota
ModReadonly
ModMod
ModVendor
)
// argsForGoVersion returns the arguments to pass to the Go compiler for the given `ModMode` and
// Go version
func (m ModMode) argsForGoVersion(version string) []string {
switch m {
case ModUnset:
return []string{}
case ModReadonly:
return []string{"-mod=readonly"}
case ModMod:
if !semver.IsValid(version) {
log.Fatalf("Invalid Go semver: '%s'", version)
}
if semver.Compare(version, "v1.14") < 0 {
return []string{} // -mod=mod is the default behaviour for go <= 1.13, and is not accepted as an argument
} else {
return []string{"-mod=mod"}
}
case ModVendor:
return []string{"-mod=vendor"}
}
return nil
}
type BuildInfo struct {
DepMode DependencyInstallerMode
ModMode ModMode
BaseDir string
}
// addVersionToMod add a go version directive, e.g. `go 1.14` to a `go.mod` file.
func addVersionToMod(version string) bool {
cmd := exec.Command("go", "mod", "edit", "-go="+version)
@@ -263,169 +176,9 @@ func getSourceDir() string {
return srcdir
}
func getDirs(paths []string) []string {
dirs := make([]string, len(paths))
for i, path := range paths {
dirs[i] = filepath.Dir(path)
}
return dirs
}
func checkDirsNested(inputDirs []string) (string, bool) {
// replace "." with "" so that we can check if all the paths are nested
dirs := make([]string, len(inputDirs))
for i, inputDir := range inputDirs {
if inputDir == "." {
dirs[i] = ""
} else {
dirs[i] = inputDir
}
}
// the paths were generated by a depth-first search so I think they might
// be sorted, but we sort them just in case
sort.Strings(dirs)
for _, dir := range dirs {
if !strings.HasPrefix(dir, dirs[0]) {
return "", false
}
}
return dirs[0], true
}
// Returns the directory to run the go build in and whether to use a go.mod
// file.
func findGoModFiles(emitDiagnostics bool) (baseDir string, useGoMod bool) {
goModPaths := util.FindAllFilesWithName(".", "go.mod", "vendor")
if len(goModPaths) == 0 {
baseDir = "."
useGoMod = false
return
}
goModDirs := getDirs(goModPaths)
if util.AnyGoFilesOutsideDirs(".", goModDirs...) {
if emitDiagnostics {
diagnostics.EmitGoFilesOutsideGoModules(goModPaths)
}
baseDir = "."
useGoMod = false
return
}
if len(goModPaths) > 1 {
// currently not supported
baseDir = "."
commonRoot, nested := checkDirsNested(goModDirs)
if nested && commonRoot == "" {
useGoMod = true
} else {
useGoMod = false
}
if emitDiagnostics {
if nested {
diagnostics.EmitMultipleGoModFoundNested(goModPaths)
} else {
diagnostics.EmitMultipleGoModFoundNotNested(goModPaths)
}
}
return
}
if emitDiagnostics {
if goModDirs[0] == "." {
diagnostics.EmitSingleRootGoModFound(goModPaths[0])
} else {
diagnostics.EmitSingleNonRootGoModFound(goModPaths[0])
}
}
baseDir = goModDirs[0]
useGoMod = true
return
}
// Returns the appropriate DependencyInstallerMode for the current project
func getDepMode(emitDiagnostics bool) (DependencyInstallerMode, string) {
bazelPaths := util.FindAllFilesWithName(".", "BUILD", "vendor")
bazelPaths = append(bazelPaths, util.FindAllFilesWithName(".", "BUILD.bazel", "vendor")...)
if len(bazelPaths) > 0 {
// currently not supported
if emitDiagnostics {
diagnostics.EmitBazelBuildFilesFound(bazelPaths)
}
}
goWorkPaths := util.FindAllFilesWithName(".", "go.work", "vendor")
if len(goWorkPaths) > 0 {
// currently not supported
if emitDiagnostics {
diagnostics.EmitGoWorkFound(goWorkPaths)
}
}
baseDir, useGoMod := findGoModFiles(emitDiagnostics)
if useGoMod {
log.Println("Found go.mod, enabling go modules")
return GoGetWithModules, baseDir
}
if util.FileExists("Gopkg.toml") {
if emitDiagnostics {
diagnostics.EmitGopkgTomlFound()
}
log.Println("Found Gopkg.toml, using dep instead of go get")
return Dep, "."
}
if util.FileExists("glide.yaml") {
if emitDiagnostics {
diagnostics.EmitGlideYamlFound()
}
log.Println("Found glide.yaml, using Glide instead of go get")
return Glide, "."
}
return GoGetNoModules, "."
}
type GoVersionInfo struct {
// The version string, if any
Version string
// A value indicating whether a version string was found
Found bool
}
// Tries to open `go.mod` and read a go directive, returning the version and whether it was found.
func tryReadGoDirective(buildInfo BuildInfo) GoVersionInfo {
if buildInfo.DepMode == GoGetWithModules {
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+(\.[0-9]+)?)$`)
goMod, err := os.ReadFile(filepath.Join(buildInfo.BaseDir, "go.mod"))
if err != nil {
log.Println("Failed to read go.mod to check for missing Go version")
} else {
matches := versionRe.FindSubmatch(goMod)
if matches != nil {
if len(matches) > 1 {
return GoVersionInfo{string(matches[1]), true}
}
}
}
}
return GoVersionInfo{"", false}
}
// Returns the appropriate ModMode for the current project
func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode {
if depMode == GoGetWithModules {
// if a vendor/modules.txt file exists, we assume that there are vendored Go dependencies, and
// skip the dependency installation step and run the extractor with `-mod=vendor`
if util.FileExists(filepath.Join(baseDir, "vendor", "modules.txt")) {
return ModVendor
} else if util.DirExists(filepath.Join(baseDir, "vendor")) {
return ModMod
}
}
return ModUnset
}
// fixGoVendorIssues fixes issues with go vendor for go version >= 1.14
func fixGoVendorIssues(buildInfo *BuildInfo, goModVersionFound bool) {
if buildInfo.ModMode == ModVendor {
func fixGoVendorIssues(buildInfo *project.BuildInfo, goModVersionFound bool) {
if buildInfo.ModMode == project.ModVendor {
// fix go vendor issues with go versions >= 1.14 when no go version is specified in the go.mod
// if this is the case, and dependencies were vendored with an old go version (and therefore
// do not contain a '## explicit' annotation, the go command will fail and refuse to do any
@@ -433,7 +186,7 @@ func fixGoVendorIssues(buildInfo *BuildInfo, goModVersionFound bool) {
//
// we work around this by adding an explicit go version of 1.13, which is the last version
// where this is not an issue
if buildInfo.DepMode == GoGetWithModules {
if buildInfo.DepMode == project.GoGetWithModules {
if !goModVersionFound {
// if the go.mod does not contain a version line
modulesTxt, err := os.ReadFile("vendor/modules.txt")
@@ -444,7 +197,7 @@ func fixGoVendorIssues(buildInfo *BuildInfo, goModVersionFound bool) {
log.Println("Adding a version directive to the go.mod file as the modules.txt does not have explicit annotations")
if !addVersionToMod("1.13") {
log.Println("Failed to add a version to the go.mod file to fix explicitly required package bug; not using vendored dependencies")
buildInfo.ModMode = ModMod
buildInfo.ModMode = project.ModMod
}
}
}
@@ -453,9 +206,9 @@ func fixGoVendorIssues(buildInfo *BuildInfo, goModVersionFound bool) {
}
// Determines whether the project needs a GOPATH set up
func getNeedGopath(buildInfo BuildInfo, importpath string) bool {
func getNeedGopath(buildInfo project.BuildInfo, importpath string) bool {
needGopath := true
if buildInfo.DepMode == GoGetWithModules {
if buildInfo.DepMode == project.GoGetWithModules {
needGopath = false
}
// if `LGTM_INDEX_NEED_GOPATH` is set, it overrides the value for `needGopath` inferred above
@@ -476,9 +229,9 @@ func getNeedGopath(buildInfo BuildInfo, importpath string) bool {
}
// Try to update `go.mod` and `go.sum` if the go version is >= 1.16.
func tryUpdateGoModAndGoSum(buildInfo BuildInfo) {
func tryUpdateGoModAndGoSum(buildInfo project.BuildInfo) {
// Go 1.16 and later won't automatically attempt to update go.mod / go.sum during package loading, so try to update them here:
if buildInfo.ModMode != ModVendor && buildInfo.DepMode == GoGetWithModules && semver.Compare(getEnvGoSemVer(), "v1.16") >= 0 {
if buildInfo.ModMode != project.ModVendor && buildInfo.DepMode == project.GoGetWithModules && semver.Compare(getEnvGoSemVer(), "v1.16") >= 0 {
// stat go.mod and go.sum
goModPath := filepath.Join(buildInfo.BaseDir, "go.mod")
beforeGoModFileInfo, beforeGoModErr := os.Stat(goModPath)
@@ -642,7 +395,7 @@ func setGopath(root string) {
// Try to build the project without custom commands. If that fails, return a boolean indicating
// that we should install dependencies ourselves.
func buildWithoutCustomCommands(modMode ModMode) bool {
func buildWithoutCustomCommands(modMode project.ModMode) bool {
shouldInstallDependencies := false
// try to build the project
buildSucceeded := autobuilder.Autobuild()
@@ -653,7 +406,7 @@ func buildWithoutCustomCommands(modMode ModMode) bool {
log.Println("Build failed, continuing to install dependencies.")
shouldInstallDependencies = true
} else if util.DepErrors("./...", modMode.argsForGoVersion(getEnvGoSemVer())...) {
} else if util.DepErrors("./...", modMode.ArgsForGoVersion(getEnvGoSemVer())...) {
log.Println("Dependencies are still not resolving after the build, continuing to install dependencies.")
shouldInstallDependencies = true
@@ -696,10 +449,10 @@ func buildWithCustomCommands(inst string) {
}
// Install dependencies using the given dependency installer mode.
func installDependencies(buildInfo BuildInfo) {
func installDependencies(buildInfo project.BuildInfo) {
// automatically determine command to install dependencies
var install *exec.Cmd
if buildInfo.DepMode == Dep {
if buildInfo.DepMode == project.Dep {
// set up the dep cache if SEMMLE_CACHE is set
cacheDir := os.Getenv("SEMMLE_CACHE")
if cacheDir != "" {
@@ -729,14 +482,14 @@ func installDependencies(buildInfo BuildInfo) {
install = exec.Command("dep", "ensure", "-v")
}
log.Println("Installing dependencies using `dep ensure`.")
} else if buildInfo.DepMode == Glide {
} else if buildInfo.DepMode == project.Glide {
install = exec.Command("glide", "install")
log.Println("Installing dependencies using `glide install`")
} else {
// explicitly set go module support
if buildInfo.DepMode == GoGetWithModules {
if buildInfo.DepMode == project.GoGetWithModules {
os.Setenv("GO111MODULE", "on")
} else if buildInfo.DepMode == GoGetNoModules {
} else if buildInfo.DepMode == project.GoGetNoModules {
os.Setenv("GO111MODULE", "off")
}
@@ -749,15 +502,15 @@ func installDependencies(buildInfo BuildInfo) {
}
// Run the extractor.
func extract(buildInfo BuildInfo) {
func extract(buildInfo project.BuildInfo) {
extractor, err := util.GetExtractorPath()
if err != nil {
log.Fatalf("Could not determine path of extractor: %v.\n", err)
}
extractorArgs := []string{}
if buildInfo.DepMode == GoGetWithModules {
extractorArgs = append(extractorArgs, buildInfo.ModMode.argsForGoVersion(getEnvGoSemVer())...)
if buildInfo.DepMode == project.GoGetWithModules {
extractorArgs = append(extractorArgs, buildInfo.ModMode.ArgsForGoVersion(getEnvGoSemVer())...)
}
extractorArgs = append(extractorArgs, "./...")
@@ -772,15 +525,9 @@ func extract(buildInfo BuildInfo) {
}
}
func getBuildInfo(emitDiagnostics bool) BuildInfo {
depMode, baseDir := getDepMode(true)
modMode := getModMode(depMode, baseDir)
return BuildInfo{depMode, modMode, baseDir}
}
// Build the project and run the extractor.
func installDependenciesAndBuild() {
log.Printf("Autobuilder was built with %s, environment has %s\n", runtime.Version(), getEnvGoVersion())
log.Printf("Autobuilder was built with %s, environment has %s\n", runtime.Version(), toolchain.GetEnvGoVersion())
srcdir := getSourceDir()
@@ -789,17 +536,20 @@ func installDependenciesAndBuild() {
// determine how to install dependencies and whether a GOPATH needs to be set up before
// extraction
buildInfo := getBuildInfo(true)
buildInfo := project.GetBuildInfo(true)
if _, present := os.LookupEnv("GO111MODULE"); !present {
os.Setenv("GO111MODULE", "auto")
}
goVersionInfo := tryReadGoDirective(buildInfo)
goVersionInfo := project.TryReadGoDirective(buildInfo)
// This diagnostic is not required if the system Go version is 1.21 or greater, since the
// Go tooling should install required Go versions as needed.
if semver.Compare(getEnvGoSemVer(), "v1.21.0") < 0 && goVersionInfo.Found && semver.Compare("v"+goVersionInfo.Version, getEnvGoSemVer()) > 0 {
diagnostics.EmitNewerGoVersionNeeded()
if val, _ := os.LookupEnv("GITHUB_ACTIONS"); val == "true" {
log.Printf("The go.mod version is newer than the installed version of Go. Consider adding an actions/setup-go step to your workflow.\n")
}
}
fixGoVendorIssues(&buildInfo, goVersionInfo.Found)
@@ -833,18 +583,18 @@ func installDependenciesAndBuild() {
buildWithCustomCommands(inst)
}
if buildInfo.ModMode == ModVendor {
if buildInfo.ModMode == project.ModVendor {
// test if running `go` with -mod=vendor works, and if it doesn't, try to fallback to -mod=mod
// or not set if the go version < 1.14. Note we check this post-build in case the build brings
// the vendor directory up to date.
if !checkVendor() {
buildInfo.ModMode = ModMod
buildInfo.ModMode = project.ModMod
log.Println("The vendor directory is not consistent with the go.mod; not using vendored dependencies.")
}
}
if shouldInstallDependencies {
if buildInfo.ModMode == ModVendor {
if buildInfo.ModMode == project.ModVendor {
log.Printf("Skipping dependency installation because a Go vendor directory was found.")
} else {
installDependencies(buildInfo)
@@ -854,288 +604,11 @@ func installDependenciesAndBuild() {
extract(buildInfo)
}
const minGoVersion = "1.11"
const maxGoVersion = "1.21"
// Check if `version` is lower than `minGoVersion`. Note that for this comparison we ignore the
// patch part of the version, so 1.20.1 and 1.20 are considered equal.
func belowSupportedRange(version string) bool {
return semver.Compare(semver.MajorMinor("v"+version), "v"+minGoVersion) < 0
}
// Check if `version` is higher than `maxGoVersion`. Note that for this comparison we ignore the
// patch part of the version, so 1.20.1 and 1.20 are considered equal.
func aboveSupportedRange(version string) bool {
return semver.Compare(semver.MajorMinor("v"+version), "v"+maxGoVersion) > 0
}
// Check if `version` is lower than `minGoVersion` or higher than `maxGoVersion`. Note that for
// this comparison we ignore the patch part of the version, so 1.20.1 and 1.20 are considered
// equal.
func outsideSupportedRange(version string) bool {
return belowSupportedRange(version) || aboveSupportedRange(version)
}
// Assuming `v.goModVersionFound` is false, emit a diagnostic and return the version to install,
// or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionNotFound(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// There is no Go version installed in the environment. We have no indication which version
// was intended to be used to build this project. Go versions are generally backwards
// compatible, so we install the maximum supported version.
msg = "No version of Go installed and no `go.mod` file found. Requesting the maximum " +
"supported version of Go (" + maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitNoGoModAndNoGoEnv(msg)
} else if outsideSupportedRange(v.goEnvVersion) {
// The Go version installed in the environment is not supported. We have no indication
// which version was intended to be used to build this project. Go versions are generally
// backwards compatible, so we install the maximum supported version.
msg = "No `go.mod` file found. The version of Go installed in the environment (" +
v.goEnvVersion + ") is outside of the supported range (" + minGoVersion + "-" +
maxGoVersion + "). Requesting the maximum supported version of Go (" + maxGoVersion +
")."
version = maxGoVersion
diagnostics.EmitNoGoModAndGoEnvUnsupported(msg)
} else {
// The version of Go that is installed is supported. We have no indication which version
// was intended to be used to build this project. We assume that the installed version is
// suitable and do not install a version of Go.
msg = "No `go.mod` file found. Version " + v.goEnvVersion + " installed in the " +
"environment is supported. Not requesting any version of Go."
version = ""
diagnostics.EmitNoGoModAndGoEnvSupported(msg)
}
return msg, version
}
// Assuming `v.goModVersion` is above the supported range, emit a diagnostic and return the
// version to install, or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionTooHigh(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// The version in the `go.mod` file is above the supported range. There is no Go version
// installed. We install the maximum supported version as a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). No version of Go installed. Requesting the maximum supported version of Go (" +
maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitGoModVersionTooHighAndNoGoEnv(msg)
} else if aboveSupportedRange(v.goEnvVersion) {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is above the supported range. We do not install a version of Go.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooHigh(msg)
} else if belowSupportedRange(v.goEnvVersion) {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is below the supported range. We install the maximum supported version as
// a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
"). Requesting the maximum supported version of Go (" + maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooLow(msg)
} else if semver.Compare("v"+maxGoVersion, "v"+v.goEnvVersion) > 0 {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is supported and below the maximum supported version. We install the
// maximum supported version as a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is below the maximum supported version (" + maxGoVersion +
"). Requesting the maximum supported version of Go (" + maxGoVersion + ")."
version = maxGoVersion
diagnostics.EmitGoModVersionTooHighAndEnvVersionBelowMax(msg)
} else {
// The version in the `go.mod` file is above the supported range. The version of Go that
// is installed is the maximum supported version. We do not install a version of Go.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is the maximum supported version (" + maxGoVersion +
"). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionTooHighAndEnvVersionMax(msg)
}
return msg, version
}
// Assuming `v.goModVersion` is below the supported range, emit a diagnostic and return the
// version to install, or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionTooLow(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// There is no Go version installed. The version in the `go.mod` file is below the
// supported range. Go versions are generally backwards compatible, so we install the
// minimum supported version.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
"). No version of Go installed. Requesting the minimum supported version of Go (" +
minGoVersion + ")."
version = minGoVersion
diagnostics.EmitGoModVersionTooLowAndNoGoEnv(msg)
} else if outsideSupportedRange(v.goEnvVersion) {
// The version of Go that is installed is outside of the supported range. The version
// in the `go.mod` file is below the supported range. Go versions are generally
// backwards compatible, so we install the minimum supported version.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
"). The version of Go installed in the environment (" + v.goEnvVersion +
") is outside of the supported range (" + minGoVersion + "-" + maxGoVersion + "). " +
"Requesting the minimum supported version of Go (" + minGoVersion + ")."
version = minGoVersion
diagnostics.EmitGoModVersionTooLowAndEnvVersionUnsupported(msg)
} else {
// The version of Go that is installed is supported. The version in the `go.mod` file is
// below the supported range. We do not install a version of Go.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is supported and is high enough for the version found in the `go.mod` file (" +
v.goModVersion + "). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionTooLowAndEnvVersionSupported(msg)
}
return msg, version
}
// Assuming `v.goModVersion` is in the supported range, emit a diagnostic and return the version
// to install, or the empty string if we should not attempt to install a version of Go.
func getVersionWhenGoModVersionSupported(v versionInfo) (msg, version string) {
if !v.goEnvVersionFound {
// There is no Go version installed. The version in the `go.mod` file is supported.
// We install the version from the `go.mod` file.
msg = "No version of Go installed. Requesting the version of Go found in the `go.mod` " +
"file (" + v.goModVersion + ")."
version = v.goModVersion
diagnostics.EmitGoModVersionSupportedAndNoGoEnv(msg)
} else if outsideSupportedRange(v.goEnvVersion) {
// The version of Go that is installed is outside of the supported range. The version in
// the `go.mod` file is supported. We install the version from the `go.mod` file.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is outside of the supported range (" + minGoVersion + "-" + maxGoVersion + "). " +
"Requesting the version of Go from the `go.mod` file (" +
v.goModVersion + ")."
version = v.goModVersion
diagnostics.EmitGoModVersionSupportedAndGoEnvUnsupported(msg)
} else if semver.Compare("v"+v.goModVersion, "v"+v.goEnvVersion) > 0 {
// The version of Go that is installed is supported. The version in the `go.mod` file is
// supported and is higher than the version that is installed. We install the version from
// the `go.mod` file.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is lower than the version found in the `go.mod` file (" + v.goModVersion +
"). Requesting the version of Go from the `go.mod` file (" + v.goModVersion + ")."
version = v.goModVersion
diagnostics.EmitGoModVersionSupportedHigherGoEnv(msg)
} else {
// The version of Go that is installed is supported. The version in the `go.mod` file is
// supported and is lower than or equal to the version that is installed. We do not install
// a version of Go.
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
") is supported and is high enough for the version found in the `go.mod` file (" +
v.goModVersion + "). Not requesting any version of Go."
version = ""
diagnostics.EmitGoModVersionSupportedLowerEqualGoEnv(msg)
}
return msg, version
}
// Check the versions of Go found in the environment and in the `go.mod` file, and return a
// version to install. If the version is the empty string then no installation is required.
// We never return a version of Go that is outside of the supported range.
//
// +-----------------------+-----------------------+-----------------------+-----------------------------------------------------+------------------------------------------------+
// | Found in go.mod > | *None* | *Below min supported* | *In supported range* | *Above max supported |
// | Installed \/ | | | | |
// |-----------------------|-----------------------|-----------------------|-----------------------------------------------------|------------------------------------------------|
// | *None* | Install max supported | Install min supported | Install version from go.mod | Install max supported |
// | *Below min supported* | Install max supported | Install min supported | Install version from go.mod | Install max supported |
// | *In supported range* | No action | No action | Install version from go.mod if newer than installed | Install max supported if newer than installed |
// | *Above max supported* | Install max supported | Install min supported | Install version from go.mod | No action |
// +-----------------------+-----------------------+-----------------------+-----------------------------------------------------+------------------------------------------------+
func getVersionToInstall(v versionInfo) (msg, version string) {
if !v.goModVersionFound {
return getVersionWhenGoModVersionNotFound(v)
}
if aboveSupportedRange(v.goModVersion) {
return getVersionWhenGoModVersionTooHigh(v)
}
if belowSupportedRange(v.goModVersion) {
return getVersionWhenGoModVersionTooLow(v)
}
return getVersionWhenGoModVersionSupported(v)
}
// Output some JSON to stdout specifying the version of Go to install, unless `version` is the
// empty string.
func outputEnvironmentJson(version string) {
var content string
if version == "" {
content = `{ "go": {} }`
} else {
content = `{ "go": { "version": "` + version + `" } }`
}
_, err := fmt.Fprint(os.Stdout, content)
if err != nil {
log.Println("Failed to write environment json to stdout: ")
log.Println(err)
}
}
type versionInfo struct {
goModVersion string // The version of Go found in the go directive in the `go.mod` file.
goModVersionFound bool // Whether a `go` directive was found in the `go.mod` file.
goEnvVersion string // The version of Go found in the environment.
goEnvVersionFound bool // Whether an installation of Go was found in the environment.
}
func (v versionInfo) String() string {
return fmt.Sprintf(
"go.mod version: %s, go.mod directive found: %t, go env version: %s, go installation found: %t",
v.goModVersion, v.goModVersionFound, v.goEnvVersion, v.goEnvVersionFound)
}
// Check if Go is installed in the environment.
func isGoInstalled() bool {
_, err := exec.LookPath("go")
return err == nil
}
// Get the version of Go to install and output it to stdout as json.
func identifyEnvironment() {
var v versionInfo
buildInfo := getBuildInfo(false)
goVersionInfo := tryReadGoDirective(buildInfo)
v.goModVersion, v.goModVersionFound = goVersionInfo.Version, goVersionInfo.Found
v.goEnvVersionFound = isGoInstalled()
if v.goEnvVersionFound {
v.goEnvVersion = getEnvGoVersion()[2:]
}
msg, versionToInstall := getVersionToInstall(v)
log.Println(msg)
outputEnvironmentJson(versionToInstall)
}
func main() {
if len(os.Args) == 1 {
installDependenciesAndBuild()
} else if len(os.Args) == 2 && os.Args[1] == "--identify-environment" {
identifyEnvironment()
autobuilder.IdentifyEnvironment()
} else {
usage()
os.Exit(2)

View File

@@ -20,61 +20,3 @@ func TestGetImportPathFromRepoURL(t *testing.T) {
}
}
}
func TestParseGoVersion(t *testing.T) {
tests := map[string]string{
"go version go1.18.9 linux/amd64": "go1.18.9",
"warning: GOPATH set to GOROOT (/usr/local/go) has no effect\ngo version go1.18.9 linux/amd64": "go1.18.9",
}
for input, expected := range tests {
actual := parseGoVersion(input)
if actual != expected {
t.Errorf("Expected parseGoVersion(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual)
}
}
}
func TestGetVersionToInstall(t *testing.T) {
tests := map[versionInfo]string{
// getVersionWhenGoModVersionNotFound()
{"", false, "", false}: maxGoVersion,
{"", false, "1.2.2", true}: maxGoVersion,
{"", false, "9999.0.1", true}: maxGoVersion,
{"", false, "1.11.13", true}: "",
{"", false, "1.20.3", true}: "",
// getVersionWhenGoModVersionTooHigh()
{"9999.0", true, "", false}: maxGoVersion,
{"9999.0", true, "9999.0.1", true}: "",
{"9999.0", true, "1.1", true}: maxGoVersion,
{"9999.0", true, minGoVersion, false}: maxGoVersion,
{"9999.0", true, maxGoVersion, true}: "",
// getVersionWhenGoModVersionTooLow()
{"0.0", true, "", false}: minGoVersion,
{"0.0", true, "9999.0", true}: minGoVersion,
{"0.0", true, "1.2.2", true}: minGoVersion,
{"0.0", true, "1.20.3", true}: "",
// getVersionWhenGoModVersionSupported()
{"1.20", true, "", false}: "1.20",
{"1.11", true, "", false}: "1.11",
{"1.20", true, "1.2.2", true}: "1.20",
{"1.11", true, "1.2.2", true}: "1.11",
{"1.20", true, "9999.0.1", true}: "1.20",
{"1.11", true, "9999.0.1", true}: "1.11",
// go.mod version > go installation version
{"1.20", true, "1.11.13", true}: "1.20",
{"1.20", true, "1.12", true}: "1.20",
// go.mod version <= go installation version (Note comparisons ignore the patch version)
{"1.11", true, "1.20", true}: "",
{"1.11", true, "1.20.3", true}: "",
{"1.20", true, "1.20.3", true}: "",
}
for input, expected := range tests {
_, actual := getVersionToInstall(input)
if actual != expected {
t.Errorf("Expected getVersionToInstall(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual)
}
}
}

View File

@@ -0,0 +1,233 @@
package project
import (
"log"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/github/codeql-go/extractor/diagnostics"
"github.com/github/codeql-go/extractor/util"
"golang.org/x/mod/semver"
)
func getDirs(paths []string) []string {
dirs := make([]string, len(paths))
for i, path := range paths {
dirs[i] = filepath.Dir(path)
}
return dirs
}
func checkDirsNested(inputDirs []string) (string, bool) {
// replace "." with "" so that we can check if all the paths are nested
dirs := make([]string, len(inputDirs))
for i, inputDir := range inputDirs {
if inputDir == "." {
dirs[i] = ""
} else {
dirs[i] = inputDir
}
}
// the paths were generated by a depth-first search so I think they might
// be sorted, but we sort them just in case
sort.Strings(dirs)
for _, dir := range dirs {
if !strings.HasPrefix(dir, dirs[0]) {
return "", false
}
}
return dirs[0], true
}
// Returns the directory to run the go build in and whether to use a go.mod
// file.
func findGoModFiles(emitDiagnostics bool) (baseDir string, useGoMod bool) {
goModPaths := util.FindAllFilesWithName(".", "go.mod", "vendor")
if len(goModPaths) == 0 {
baseDir = "."
useGoMod = false
return
}
goModDirs := getDirs(goModPaths)
if util.AnyGoFilesOutsideDirs(".", goModDirs...) {
if emitDiagnostics {
diagnostics.EmitGoFilesOutsideGoModules(goModPaths)
}
baseDir = "."
useGoMod = false
return
}
if len(goModPaths) > 1 {
// currently not supported
baseDir = "."
commonRoot, nested := checkDirsNested(goModDirs)
if nested && commonRoot == "" {
useGoMod = true
} else {
useGoMod = false
}
if emitDiagnostics {
if nested {
diagnostics.EmitMultipleGoModFoundNested(goModPaths)
} else {
diagnostics.EmitMultipleGoModFoundNotNested(goModPaths)
}
}
return
}
if emitDiagnostics {
if goModDirs[0] == "." {
diagnostics.EmitSingleRootGoModFound(goModPaths[0])
} else {
diagnostics.EmitSingleNonRootGoModFound(goModPaths[0])
}
}
baseDir = goModDirs[0]
useGoMod = true
return
}
// DependencyInstallerMode is an enum describing how dependencies should be installed
type DependencyInstallerMode int
const (
// GoGetNoModules represents dependency installation using `go get` without modules
GoGetNoModules DependencyInstallerMode = iota
// GoGetWithModules represents dependency installation using `go get` with modules
GoGetWithModules
// Dep represent dependency installation using `dep ensure`
Dep
// Glide represents dependency installation using `glide install`
Glide
)
// Returns the appropriate DependencyInstallerMode for the current project
func getDepMode(emitDiagnostics bool) (DependencyInstallerMode, string) {
bazelPaths := util.FindAllFilesWithName(".", "BUILD", "vendor")
bazelPaths = append(bazelPaths, util.FindAllFilesWithName(".", "BUILD.bazel", "vendor")...)
if len(bazelPaths) > 0 {
// currently not supported
if emitDiagnostics {
diagnostics.EmitBazelBuildFilesFound(bazelPaths)
}
}
goWorkPaths := util.FindAllFilesWithName(".", "go.work", "vendor")
if len(goWorkPaths) > 0 {
// currently not supported
if emitDiagnostics {
diagnostics.EmitGoWorkFound(goWorkPaths)
}
}
baseDir, useGoMod := findGoModFiles(emitDiagnostics)
if useGoMod {
log.Println("Found go.mod, enabling go modules")
return GoGetWithModules, baseDir
}
if util.FileExists("Gopkg.toml") {
if emitDiagnostics {
diagnostics.EmitGopkgTomlFound()
}
log.Println("Found Gopkg.toml, using dep instead of go get")
return Dep, "."
}
if util.FileExists("glide.yaml") {
if emitDiagnostics {
diagnostics.EmitGlideYamlFound()
}
log.Println("Found glide.yaml, using Glide instead of go get")
return Glide, "."
}
return GoGetNoModules, "."
}
// ModMode corresponds to the possible values of the -mod flag for the Go compiler
type ModMode int
const (
ModUnset ModMode = iota
ModReadonly
ModMod
ModVendor
)
// argsForGoVersion returns the arguments to pass to the Go compiler for the given `ModMode` and
// Go version
func (m ModMode) ArgsForGoVersion(version string) []string {
switch m {
case ModUnset:
return []string{}
case ModReadonly:
return []string{"-mod=readonly"}
case ModMod:
if !semver.IsValid(version) {
log.Fatalf("Invalid Go semver: '%s'", version)
}
if semver.Compare(version, "v1.14") < 0 {
return []string{} // -mod=mod is the default behaviour for go <= 1.13, and is not accepted as an argument
} else {
return []string{"-mod=mod"}
}
case ModVendor:
return []string{"-mod=vendor"}
}
return nil
}
// Returns the appropriate ModMode for the current project
func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode {
if depMode == GoGetWithModules {
// if a vendor/modules.txt file exists, we assume that there are vendored Go dependencies, and
// skip the dependency installation step and run the extractor with `-mod=vendor`
if util.FileExists(filepath.Join(baseDir, "vendor", "modules.txt")) {
return ModVendor
} else if util.DirExists(filepath.Join(baseDir, "vendor")) {
return ModMod
}
}
return ModUnset
}
type BuildInfo struct {
DepMode DependencyInstallerMode
ModMode ModMode
BaseDir string
}
func GetBuildInfo(emitDiagnostics bool) BuildInfo {
depMode, baseDir := getDepMode(true)
modMode := getModMode(depMode, baseDir)
return BuildInfo{depMode, modMode, baseDir}
}
type GoVersionInfo struct {
// The version string, if any
Version string
// A value indicating whether a version string was found
Found bool
}
// Tries to open `go.mod` and read a go directive, returning the version and whether it was found.
func TryReadGoDirective(buildInfo BuildInfo) GoVersionInfo {
if buildInfo.DepMode == GoGetWithModules {
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+(\.[0-9]+)?)$`)
goMod, err := os.ReadFile(filepath.Join(buildInfo.BaseDir, "go.mod"))
if err != nil {
log.Println("Failed to read go.mod to check for missing Go version")
} else {
matches := versionRe.FindSubmatch(goMod)
if matches != nil {
if len(matches) > 1 {
return GoVersionInfo{string(matches[1]), true}
}
}
}
}
return GoVersionInfo{"", false}
}

View File

@@ -0,0 +1,49 @@
package toolchain
import (
"bufio"
"log"
"os"
"os/exec"
"strings"
)
// Check if Go is installed in the environment.
func IsInstalled() bool {
_, err := exec.LookPath("go")
return err == nil
}
var goVersion = ""
// Returns the current Go version as returned by 'go version', e.g. go1.14.4
func GetEnvGoVersion() string {
if goVersion == "" {
// Since Go 1.21, running 'go version' in a directory with a 'go.mod' file will attempt to
// download the version of Go specified in there. That may either fail or result in us just
// being told what's already in 'go.mod'. Setting 'GOTOOLCHAIN' to 'local' will force it
// to use the local Go toolchain instead.
cmd := exec.Command("go", "version")
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("Unable to run the go command, is it installed?\nError: %s", err.Error())
}
goVersion = parseGoVersion(string(out))
}
return goVersion
}
// The 'go version' command may output warnings on separate lines before
// the actual version string is printed. This function parses the output
// to retrieve just the version string.
func parseGoVersion(data string) string {
var lastLine string
sc := bufio.NewScanner(strings.NewReader(data))
for sc.Scan() {
lastLine = sc.Text()
}
return strings.Fields(lastLine)[2]
}

View File

@@ -0,0 +1,16 @@
package toolchain
import "testing"
func TestParseGoVersion(t *testing.T) {
tests := map[string]string{
"go version go1.18.9 linux/amd64": "go1.18.9",
"warning: GOPATH set to GOROOT (/usr/local/go) has no effect\ngo version go1.18.9 linux/amd64": "go1.18.9",
}
for input, expected := range tests {
actual := parseGoVersion(input)
if actual != expected {
t.Errorf("Expected parseGoVersion(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual)
}
}
}

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Deleted many deprecated predicates and classes with uppercase `TLD`, `HTTP`, `SQL`, `URL` etc. in their names. Use the PascalCased versions instead.
* Deleted the deprecated and unused `Source` class from the `SharedXss` module of `Xss.qll`

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